Clean up.
This commit is contained in:
parent
610eac927b
commit
efecc24204
@ -1,25 +0,0 @@
|
||||
plugins {
|
||||
id 'ssg.common'
|
||||
id 'ssg.lib'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 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'
|
||||
|
||||
testRuntimeOnly project(':gcp-impl')
|
||||
}
|
||||
|
||||
jar {
|
||||
archivesBaseName = 'gcp-api'
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
interface Component {
|
||||
String render(Map<String, ?> attr, String body)
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
interface ComponentFactory {
|
||||
Component get()
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Marker
|
||||
import org.slf4j.MarkerFactory
|
||||
|
||||
class ComponentsContainer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ComponentsContainer)
|
||||
private static final Marker enter = MarkerFactory.getMarker('ENTER')
|
||||
private static final Marker exit = MarkerFactory.getMarker('EXIT')
|
||||
|
||||
private final Map<String, Component> componentCache = [:]
|
||||
private final GroovyClassLoader loader
|
||||
|
||||
ComponentsContainer(Collection<URL> componentDirUrls, Collection<Component> components) {
|
||||
logger.trace(enter, 'componentDirUrls: {}, components: {}', componentDirUrls, components)
|
||||
this.loader = new GroovyClassLoader()
|
||||
componentDirUrls.each { this.loader.addURL(it) }
|
||||
components.each {
|
||||
this.componentCache[it.class.simpleName] = it
|
||||
}
|
||||
logger.debug('this.loader: {}', this.loader)
|
||||
logger.debug('this.componentCache: {}', this.componentCache)
|
||||
logger.trace(exit, '')
|
||||
}
|
||||
|
||||
Component get(String name) {
|
||||
logger.trace('name: {}', name)
|
||||
def component = this.componentCache.computeIfAbsent(name, {
|
||||
def componentClass = (Class<? extends Component>) this.loader.loadClass(it)
|
||||
componentClass.getDeclaredConstructor().newInstance() // must be a default constructor (for now)
|
||||
})
|
||||
logger.trace(exit, 'component: {}', component)
|
||||
component
|
||||
}
|
||||
|
||||
Component getAt(String name) {
|
||||
this.get(name)
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
import groovy.text.Template
|
||||
|
||||
class GcpTemplate implements Template {
|
||||
|
||||
Closure templateClosure
|
||||
ComponentsContainer components
|
||||
|
||||
String renderComponent(String componentName, Closure configureComponentInstance) {
|
||||
Map<String, String> attr = [:]
|
||||
def bodyOut = new StringBuilder()
|
||||
configureComponentInstance(attr, bodyOut)
|
||||
|
||||
def component = this.components[componentName]
|
||||
component.render(attr, bodyOut.toString())
|
||||
}
|
||||
|
||||
@Override
|
||||
final Writable make() {
|
||||
this.make([:])
|
||||
}
|
||||
|
||||
@Override
|
||||
final Writable make(Map binding) {
|
||||
def rehydrated = this.templateClosure.rehydrate(binding, this, this).asWritable()
|
||||
rehydrated.setResolveStrategy(Closure.DELEGATE_FIRST)
|
||||
rehydrated
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
import groovy.text.Template
|
||||
import groovy.text.TemplateEngine
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.codehaus.groovy.control.CompilationFailedException
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.function.Supplier
|
||||
|
||||
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
|
||||
Collection<URL> componentDirUrls
|
||||
Collection<Component> components
|
||||
}
|
||||
|
||||
private final Configuration configuration
|
||||
private final File scriptsDir = File.createTempDir()
|
||||
private final AtomicInteger templateCount = new AtomicInteger(0)
|
||||
private final GroovyScriptEngine scriptEngine
|
||||
|
||||
GcpTemplateEngine(Configuration configuration) {
|
||||
this.configuration = configuration
|
||||
this.scriptEngine = new GroovyScriptEngine([this.scriptsDir.toURI().toURL()] as URL[])
|
||||
}
|
||||
|
||||
@Override
|
||||
Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
|
||||
def templateSrc = reader.text
|
||||
|
||||
def converter = getConverter()
|
||||
def scriptSrc = converter.convert(templateSrc)
|
||||
logger.debug('scriptSrc: {}', scriptSrc)
|
||||
def scriptName = "SsgTemplate${ this.templateCount.getAndIncrement() }.groovy"
|
||||
new File(this.scriptsDir, scriptName).write(scriptSrc)
|
||||
|
||||
def script = this.scriptEngine.createScript(scriptName, new Binding())
|
||||
|
||||
def templateClosure = (Closure) script.invokeMethod('getTemplate', null)
|
||||
|
||||
def template = this.configuration.ssgTemplateSupplier.get()
|
||||
template.templateClosure = templateClosure
|
||||
template.components = new ComponentsContainer(this.configuration.componentDirUrls, this.configuration.components)
|
||||
|
||||
template
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
interface GcpToScriptConverter {
|
||||
String convert(String gcpSrc)
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Token {
|
||||
|
||||
enum Type {
|
||||
TEXT,
|
||||
|
||||
DOLLAR,
|
||||
GROOVY_REFERENCE,
|
||||
CURLY_OPEN,
|
||||
SCRIPTLET,
|
||||
CURLY_CLOSE,
|
||||
BLOCK_SCRIPTLET_OPEN,
|
||||
EXPRESSION_SCRIPTLET_OPEN,
|
||||
SCRIPTLET_CLOSE,
|
||||
|
||||
CLASS_NAME,
|
||||
PACKAGE_NAME,
|
||||
DOT,
|
||||
|
||||
WHITESPACE,
|
||||
|
||||
KEY,
|
||||
EQUALS,
|
||||
|
||||
DOUBLE_QUOTE,
|
||||
STRING,
|
||||
SINGLE_QUOTE,
|
||||
|
||||
COMPONENT_START,
|
||||
FORWARD_SLASH,
|
||||
COMPONENT_END,
|
||||
;
|
||||
|
||||
boolean isAnyOf(Collection<Type> types) {
|
||||
return types.contains(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Type getType();
|
||||
CharSequence getText();
|
||||
int getInputIndex();
|
||||
int getLine();
|
||||
int getCol();
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
public interface Tokenizer {
|
||||
|
||||
enum State {
|
||||
TEXT,
|
||||
COMPONENT_NAME,
|
||||
COMPONENT_KEYS_AND_VALUES
|
||||
}
|
||||
|
||||
void start(CharSequence input, int startOffset, int endOffset, State initialState);
|
||||
boolean hasNext();
|
||||
Token next();
|
||||
State getCurrentState();
|
||||
|
||||
default Queue<Token> tokenizeAll(CharSequence input, State initialState) {
|
||||
this.start(input, 0, input.length(), initialState);
|
||||
final Queue<Token> tokens = new LinkedList<>();
|
||||
while (this.hasNext()) {
|
||||
tokens.add(this.next());
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
|
||||
import groovy.text.TemplateEngine
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
class GcpTemplateEngineIntegrationTests {
|
||||
|
||||
private final TemplateEngine engine = new GcpTemplateEngine(new GcpTemplateEngine.Configuration(
|
||||
{ new GcpTemplate() },
|
||||
[],
|
||||
[]
|
||||
))
|
||||
|
||||
@Test
|
||||
void doctype() {
|
||||
def src = '<!DOCTYPE html>'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals('<!DOCTYPE html>', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesNewlines() {
|
||||
def src = '<!DOCTYPE html>\n<html>'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals(src, r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyScriptlet() {
|
||||
def src = '<%%>'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals('', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleOut() {
|
||||
def src = '<% out << "Hello, World!" %>'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, World!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void scriptletInString() {
|
||||
def src = '<html lang="<% out << "en" %>">'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals('<html lang="en">', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void expressionScriptlet() {
|
||||
def src = '<%= 13 %>'
|
||||
def r = this.engine.createTemplate(src).make().toString()
|
||||
assertEquals('13', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingWorks() {
|
||||
def src = '<%= greeting %>'
|
||||
def r = this.engine.createTemplate(src).make([greeting: 'Hello, World!']).toString()
|
||||
assertEquals('Hello, World!', r)
|
||||
}
|
||||
|
||||
static class CustomBaseTemplate extends GcpTemplate {
|
||||
|
||||
def greeting = 'Greetings!'
|
||||
def name = 'Jesse'
|
||||
|
||||
@SuppressWarnings('GrMethodMayBeStatic')
|
||||
def greet() {
|
||||
'Hello, World!'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseTemplateMethodsPresent() {
|
||||
def src = '<%= greet() %>'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new CustomBaseTemplate() }, [], [])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, World!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseTemplatePropertiesPresent() {
|
||||
def src = '<%= this.greeting %>'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new CustomBaseTemplate() }, [], [])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Greetings!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingOverridesCustomBaseTemplate() {
|
||||
def src = '<%= greet() %>'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new CustomBaseTemplate() }, [], [])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make([greet: { "Hello, Person!" }]).toString()
|
||||
assertEquals('Hello, Person!', r)
|
||||
}
|
||||
|
||||
static class Greeter implements Component {
|
||||
|
||||
@Override
|
||||
String render(Map<String, ?> attr, String body) {
|
||||
"${ attr.greeting }, ${ attr.person }!"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfClosingComponent() {
|
||||
def src = '<Greeter greeting="Hello" person="World" />'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new GcpTemplate() }, [], [new Greeter()])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, World!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGStringAttrValue() {
|
||||
def src = '<Greeter greeting="Hello" person="person number ${ 13 }" />'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new GcpTemplate() }, [], [new Greeter()])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, person number 13!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGStringAttrValueCanAccessBinding() {
|
||||
def src = '<Greeter greeting="Hello" person="person named ${ name }" />'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new GcpTemplate() }, [], [new Greeter()])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make([name: 'Jesse']).toString()
|
||||
assertEquals('Hello, person named Jesse!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGStringAttrValueCanAccessBaseTemplateMethod() {
|
||||
def src = '<Greeter greeting="Hello" person="person named ${ getName() }" />'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new CustomBaseTemplate() }, [], [new Greeter()])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, person named Jesse!', r)
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGStringAttrValueCanAccessBaseTemplateProperty() {
|
||||
def src = '<Greeter greeting="Hello" person="person named ${ this.name }" />'
|
||||
def configuration = new GcpTemplateEngine.Configuration({ new CustomBaseTemplate() }, [], [new Greeter()])
|
||||
def engine = new GcpTemplateEngine(configuration)
|
||||
def r = engine.createTemplate(src).make().toString()
|
||||
assertEquals('Hello, person named Jesse!', r)
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package components
|
||||
|
||||
import com.jessebrault.gcp.Component
|
||||
|
||||
class Greeting implements Component {
|
||||
|
||||
@Override
|
||||
String render(Map<String, ?> attr, String body) {
|
||||
"<h1>${ attr.person }, ${ attr.person }!</h1>"
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package components
|
||||
|
||||
import com.jessebrault.gcp.Component
|
||||
|
||||
class Head implements Component {
|
||||
|
||||
@Override
|
||||
String render(Map<String, ?> attr, String body) {
|
||||
def b = new StringBuilder()
|
||||
b << '<head>\n'
|
||||
b << " <title>${ attr.title }</title>\n"
|
||||
b << '</head>\n'
|
||||
b.toString()
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration name="ssg" status="WARN">
|
||||
<Appenders>
|
||||
<Console name="standard" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger{1} %M %L: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger{1} %M %L: %markerSimpleName %msg%n%ex" />
|
||||
</MarkerPatternSelector>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="standard" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<Head val=${ frontMatter.test } val=$test val="Hello!" val='Hello!'>
|
||||
<meta val=${ binding.someVar } />
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,7 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<Head title="Greeting Page" />
|
||||
<body>
|
||||
<Greeting person="World" />
|
||||
</body>
|
||||
</html>
|
@ -1,25 +0,0 @@
|
||||
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'
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
package com.jessebrault.gcp
|
||||
|
||||
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilder
|
||||
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilderImpl
|
||||
import com.jessebrault.gcp.util.PatternFunction
|
||||
|
||||
class GcpToScriptConverterImpl implements GcpToScriptConverter {
|
||||
|
||||
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(~/^<%(?!=)/)
|
||||
private static final PatternFunction expressionScriptletOpen = new PatternFunction(~/^<%=/)
|
||||
private static final PatternFunction scriptletText = new PatternFunction(~/^.+(?=%>)/)
|
||||
private static final PatternFunction scriptletClose = new PatternFunction(~/^%>/)
|
||||
|
||||
private static final PatternFunction componentOpen = new PatternFunction(~/^<(?=\p{Lu})/)
|
||||
private static final PatternFunction componentIdentifier = new PatternFunction(~/^\p{Lu}.*?(?=\s|\\/)/)
|
||||
private static final PatternFunction attrKeyWithValue = new PatternFunction(~/^\s*[\p{Ll}\p{Lu}0-9_\-]+=/)
|
||||
private static final PatternFunction attrKeyBoolean = new PatternFunction(~/^\s*[\p{Ll}\p{Lu}0-9_\-]++(?!=)/)
|
||||
private static final PatternFunction componentSelfClose = new PatternFunction(~/^\s*\/>/)
|
||||
|
||||
private static final PatternFunction attrValueStringOpen = new PatternFunction(~/^["']/)
|
||||
private static final PatternFunction attrValueStringContents = new PatternFunction(~/^(?:[\w\W&&[^\\"]]|\\\\|\\")*(?=")/)
|
||||
private static final PatternFunction attrValueStringClose = new PatternFunction(~/["']/)
|
||||
|
||||
private static StackFunctionFsmBuilder<String, State, String> getFsmBuilder() {
|
||||
new StackFunctionFsmBuilderImpl<>()
|
||||
}
|
||||
|
||||
@Override
|
||||
String convert(String src) {
|
||||
def b = new StringBuilder()
|
||||
def stringAcc = new StringBuilder()
|
||||
|
||||
b << 'def getTemplate() {\nreturn { out ->\n'
|
||||
|
||||
def fsm = getFsmBuilder().with {
|
||||
initialState = State.HTML
|
||||
|
||||
whileIn(State.HTML) {
|
||||
on html exec {
|
||||
stringAcc << it
|
||||
}
|
||||
on scriptletOpen shiftTo State.SCRIPTLET exec {
|
||||
if (stringAcc.length() > 0) {
|
||||
b << 'out << """' << stringAcc.toString() << '""";\n'
|
||||
stringAcc = new StringBuilder()
|
||||
}
|
||||
}
|
||||
on expressionScriptletOpen shiftTo State.EXPRESSION_SCRIPTLET exec {
|
||||
stringAcc << '${'
|
||||
}
|
||||
on componentOpen shiftTo State.COMPONENT_IDENTIFIER exec {
|
||||
if (stringAcc.length() > 0) {
|
||||
b << 'out << """' << stringAcc.toString() << '""";\n'
|
||||
stringAcc = new StringBuilder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.SCRIPTLET) {
|
||||
on scriptletText exec {
|
||||
b << it
|
||||
}
|
||||
on scriptletClose shiftTo HTML exec {
|
||||
b << ';\n'
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.EXPRESSION_SCRIPTLET) {
|
||||
on scriptletText exec {
|
||||
stringAcc << it
|
||||
}
|
||||
on scriptletClose shiftTo HTML exec {
|
||||
stringAcc << '}'
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.COMPONENT) {
|
||||
// tokenize component, figure out body, and tokenize closing component
|
||||
}
|
||||
|
||||
whileIn(COMPONENT_IDENTIFIER) {
|
||||
on componentIdentifier shiftTo COMPONENT_ATTR_KEY exec {
|
||||
b << "out << renderComponent('${ it }') { attr, bodyOut ->\n"
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new RuntimeException('expected a Component Identifier')
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(COMPONENT_ATTR_KEY) {
|
||||
on attrKeyWithValue shiftTo COMPONENT_ATTR_VALUE_OPEN exec { String s ->
|
||||
def trimmed = s.trim()
|
||||
def key = trimmed.substring(0, trimmed.length() - 1)
|
||||
b << "attr['${ key }'] = "
|
||||
}
|
||||
on attrKeyBoolean exec { String s ->
|
||||
def trimmed = s.trim()
|
||||
def key = trimmed.substring(0, trimmed.length() - 1)
|
||||
b << "attr['${ key }'] = true"
|
||||
}
|
||||
on componentSelfClose shiftTo HTML exec {
|
||||
b << '};\n'
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new RuntimeException('expected either an attr key or a closing />')
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(COMPONENT_ATTR_VALUE_OPEN) {
|
||||
on attrValueStringOpen shiftTo COMPONENT_ATTR_VALUE_STRING exec {
|
||||
b << '"'
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new RuntimeException('expected a string opening')
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(COMPONENT_ATTR_VALUE_STRING) {
|
||||
on attrValueStringContents shiftTo COMPONENT_ATTR_VALUE_STRING_CLOSE exec {
|
||||
b << it
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new RuntimeException('expected string contents')
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(COMPONENT_ATTR_VALUE_STRING_CLOSE) {
|
||||
on attrValueStringClose shiftTo COMPONENT_ATTR_KEY exec {
|
||||
b << '";\n'
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new RuntimeException('expected string close')
|
||||
}
|
||||
}
|
||||
|
||||
build()
|
||||
}
|
||||
|
||||
def remaining = src
|
||||
while (remaining.length() > 0) {
|
||||
def output = fsm.apply(remaining)
|
||||
if (output != null) {
|
||||
remaining = remaining.substring(output.length())
|
||||
} else if (output != null && output.length() == 0) {
|
||||
throw new RuntimeException('output length is zero')
|
||||
} else {
|
||||
throw new RuntimeException('output is null')
|
||||
}
|
||||
}
|
||||
|
||||
if (fsm.currentState == HTML && stringAcc.length() > 0) {
|
||||
b << 'out << """' << stringAcc.toString() << '""";\n'
|
||||
stringAcc = new StringBuilder()
|
||||
}
|
||||
b << '}}\n'
|
||||
|
||||
b.toString()
|
||||
}
|
||||
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
import com.jessebrault.gcp.component.ComponentToken.Type
|
||||
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.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.*
|
||||
|
||||
/**
|
||||
* NOT thread safe
|
||||
*/
|
||||
@PackageScope
|
||||
class ComponentParser {
|
||||
|
||||
private Queue<ComponentToken> tokens
|
||||
private String currentIdentifier
|
||||
|
||||
ComponentRoot 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 ComponentRoot selfClosingComponent() {
|
||||
this.startOfOpeningOrSelfClosingComponent()
|
||||
def keysAndValues = this.keysAndValues()
|
||||
this.expect(FORWARD_SLASH)
|
||||
this.expect(GT)
|
||||
new ComponentRoot().tap {
|
||||
it.identifier = this.currentIdentifier
|
||||
it.children << keysAndValues
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentRoot openingComponent() {
|
||||
this.startOfOpeningOrSelfClosingComponent()
|
||||
def keysAndValues = this.keysAndValues()
|
||||
this.expect(GT)
|
||||
new ComponentRoot().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<ComponentNode> 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 ComponentNode value() {
|
||||
if (this.peek(DOUBLE_QUOTE)) {
|
||||
return this.doubleQuoteStringValue()
|
||||
} else if (this.peek(SINGLE_QUOTE)) {
|
||||
return this.singleQuoteStringValue()
|
||||
} else if (this.peek(GROOVY_IDENTIFIER)) {
|
||||
return this.dollarReferenceValue()
|
||||
} else if (this.peek(GROOVY)) {
|
||||
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, GROOVY_IDENTIFIER, GROOVY, 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([GROOVY_IDENTIFIER])
|
||||
private DollarReferenceValue dollarReferenceValue() {
|
||||
def groovyIdentifierToken = this.expect(GROOVY_IDENTIFIER)
|
||||
new DollarReferenceValue().tap {
|
||||
reference = groovyIdentifierToken.text
|
||||
}
|
||||
}
|
||||
|
||||
@PeekBefore([GROOVY])
|
||||
private DollarScriptletValue dollarScriptletValue() {
|
||||
def groovyToken = this.expect(GROOVY)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
import com.jessebrault.gcp.component.node.BooleanValue
|
||||
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.ComponentNodeVisitor
|
||||
import com.jessebrault.gcp.component.node.ScriptletValue
|
||||
import com.jessebrault.gcp.component.node.StringValue
|
||||
|
||||
// NOT THREAD SAFE
|
||||
class ComponentToClosureVisitor extends ComponentNodeVisitor {
|
||||
|
||||
private StringBuilder b = new StringBuilder()
|
||||
|
||||
String getResult() {
|
||||
b.toString()
|
||||
}
|
||||
|
||||
void reset() {
|
||||
b = new StringBuilder()
|
||||
}
|
||||
|
||||
void visit(ComponentRoot componentNode) {
|
||||
b << '{ '
|
||||
super.visit(componentNode)
|
||||
if (componentNode.body != null) {
|
||||
b << "bodyOut << ${ componentNode.body }; "
|
||||
}
|
||||
b << '};'
|
||||
}
|
||||
|
||||
void visit(KeysAndValues keysAndValues) {
|
||||
b << 'attr { '
|
||||
super.visit(keysAndValues)
|
||||
b << '}; '
|
||||
}
|
||||
|
||||
void visit(KeyAndValue keyAndValue) {
|
||||
b << "${ keyAndValue.key } = "
|
||||
super.visit(keyAndValue)
|
||||
b << '; '
|
||||
}
|
||||
|
||||
void visit(GStringValue gStringValue) {
|
||||
b << "\"${ gStringValue.gString }\""
|
||||
}
|
||||
|
||||
void visit(StringValue stringValue) {
|
||||
b << "'${ stringValue.string }'"
|
||||
}
|
||||
|
||||
void visit(DollarReferenceValue dollarReferenceValue) {
|
||||
b << dollarReferenceValue.reference
|
||||
}
|
||||
|
||||
void visit(DollarScriptletValue dollarScriptletValue) {
|
||||
b << dollarScriptletValue.scriptlet
|
||||
}
|
||||
|
||||
void visit(ScriptletValue scriptletValue) {
|
||||
b << "render { out -> ${ scriptletValue.scriptlet } }"
|
||||
}
|
||||
|
||||
void visit(ExpressionScriptletValue expressionScriptletValue) {
|
||||
b << expressionScriptletValue.scriptlet
|
||||
}
|
||||
|
||||
void visit(BooleanValue booleanValue) {
|
||||
b << booleanValue.value.toString()
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
@TupleConstructor
|
||||
class ComponentToken {
|
||||
|
||||
enum Type {
|
||||
LT,
|
||||
GT,
|
||||
IDENTIFIER,
|
||||
KEY,
|
||||
EQUALS,
|
||||
DOUBLE_QUOTE,
|
||||
SINGLE_QUOTE,
|
||||
STRING,
|
||||
GROOVY,
|
||||
GROOVY_IDENTIFIER,
|
||||
PERCENT,
|
||||
FORWARD_SLASH
|
||||
;
|
||||
|
||||
boolean isAnyOf(Collection<Type> types) {
|
||||
types.contains(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Type type
|
||||
String text
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"ComponentToken(${ this.type }, ${ this.text })"
|
||||
}
|
||||
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
import com.jessebrault.fsm.function.FunctionFsmBuilder
|
||||
import com.jessebrault.fsm.function.FunctionFsmBuilderImpl
|
||||
import com.jessebrault.gcp.groovy.DollarScriptletParser
|
||||
import com.jessebrault.gcp.util.PatternFunction
|
||||
|
||||
import static ComponentToken.Type
|
||||
|
||||
class ComponentTokenizer {
|
||||
|
||||
private static final PatternFunction lessThan = new PatternFunction(~/^</)
|
||||
private static final PatternFunction greaterThan = new PatternFunction(~/^>/)
|
||||
private static final PatternFunction identifier = new PatternFunction(~/^\p{Lu}.*?(?=\s|\/)/)
|
||||
private static final PatternFunction whitespace = new PatternFunction(~/^\s+/)
|
||||
private static final PatternFunction key = new PatternFunction(~/^[\p{L}0-9_\-]+/)
|
||||
private static final PatternFunction equals = new PatternFunction(~/^=/)
|
||||
private static final PatternFunction doubleQuote = new PatternFunction(~/^"/)
|
||||
private static final PatternFunction doubleQuoteStringContent = new PatternFunction(~/^(?:[\w\W&&[^\\"]]|\\")+/)
|
||||
private static final PatternFunction singleQuote = new PatternFunction(~/^'/)
|
||||
private static final PatternFunction singleQuoteStringContent = new PatternFunction(~/^(?:[\w\W&&[^\\']]|\\')+/)
|
||||
|
||||
// https://docs.groovy-lang.org/latest/html/documentation/#_identifiers
|
||||
//'\u00C0' to '\u00D6'
|
||||
//'\u00D8' to '\u00F6'
|
||||
//'\u00F8' to '\u00FF'
|
||||
//'\u0100' to '\uFFFE'
|
||||
private static final PatternFunction dollarReference = new PatternFunction(~/^\$[a-zA-Z_$\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\ufff3][a-zA-Z_$0-9\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\ufff3]*(?=[\s\/>])/)
|
||||
|
||||
private static final PatternFunction percent = new PatternFunction(~/^%/)
|
||||
private static final PatternFunction expressionScriptletGroovy = new PatternFunction(~/^.*?%>/)
|
||||
private static final PatternFunction forwardSlash = new PatternFunction(~/^\//)
|
||||
|
||||
static enum State {
|
||||
START,
|
||||
IDENTIFIER,
|
||||
KEYS_AND_VALUES,
|
||||
DOUBLE_QUOTE_STRING,
|
||||
SINGLE_QUOTE_STRING,
|
||||
EXPRESSION_SCRIPTLET_GROOVY,
|
||||
DONE
|
||||
}
|
||||
|
||||
private static FunctionFsmBuilder<String, State, String> getFsmBuilder() {
|
||||
new FunctionFsmBuilderImpl<>()
|
||||
}
|
||||
|
||||
Queue<ComponentToken> tokenize(String src) {
|
||||
Queue<ComponentToken> tokens = new LinkedList<>()
|
||||
|
||||
def fsm = getFsmBuilder().with {
|
||||
initialState = State.START
|
||||
|
||||
whileIn(State.START) {
|
||||
on lessThan shiftTo State.IDENTIFIER exec {
|
||||
tokens << new ComponentToken(Type.LT, it)
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.IDENTIFIER) {
|
||||
on identifier shiftTo State.KEYS_AND_VALUES exec {
|
||||
tokens << new ComponentToken(Type.IDENTIFIER, it)
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.KEYS_AND_VALUES) {
|
||||
on greaterThan shiftTo State.DONE exec {
|
||||
tokens << new ComponentToken(Type.GT, it)
|
||||
}
|
||||
on whitespace exec { }
|
||||
on key exec {
|
||||
tokens << new ComponentToken(Type.KEY, it)
|
||||
}
|
||||
on equals exec {
|
||||
tokens << new ComponentToken(Type.EQUALS, it)
|
||||
}
|
||||
on doubleQuote shiftTo State.DOUBLE_QUOTE_STRING exec {
|
||||
tokens << new ComponentToken(Type.DOUBLE_QUOTE, it)
|
||||
}
|
||||
on singleQuote shiftTo State.SINGLE_QUOTE_STRING exec {
|
||||
tokens << new ComponentToken(Type.SINGLE_QUOTE, it)
|
||||
}
|
||||
on dollarReference exec { String s ->
|
||||
tokens << new ComponentToken(Type.GROOVY_IDENTIFIER, s.substring(1)) // skip opening $
|
||||
}
|
||||
//noinspection GroovyAssignabilityCheck // for some reason IntelliJ is confused by this
|
||||
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 {
|
||||
tokens << new ComponentToken(Type.PERCENT, it)
|
||||
}
|
||||
on forwardSlash exec {
|
||||
tokens << new ComponentToken(Type.FORWARD_SLASH, it)
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.DOUBLE_QUOTE_STRING) {
|
||||
on doubleQuoteStringContent exec {
|
||||
tokens << new ComponentToken(Type.STRING, it)
|
||||
}
|
||||
on doubleQuote shiftTo State.KEYS_AND_VALUES exec {
|
||||
tokens << new ComponentToken(Type.DOUBLE_QUOTE, it)
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.SINGLE_QUOTE_STRING) {
|
||||
on singleQuoteStringContent exec {
|
||||
tokens << new ComponentToken(Type.STRING, it)
|
||||
}
|
||||
on singleQuote shiftTo State.KEYS_AND_VALUES exec {
|
||||
tokens << new ComponentToken(Type.SINGLE_QUOTE, it)
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
whileIn(State.EXPRESSION_SCRIPTLET_GROOVY) {
|
||||
on expressionScriptletGroovy shiftTo State.KEYS_AND_VALUES exec { String s ->
|
||||
tokens << new ComponentToken(Type.GROOVY, s.substring(0, s.length() - 2))
|
||||
tokens << new ComponentToken(Type.PERCENT, '%')
|
||||
tokens << new ComponentToken(Type.GT, '>')
|
||||
}
|
||||
onNoMatch() exec {
|
||||
throw new IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
build()
|
||||
}
|
||||
|
||||
def remaining = src
|
||||
|
||||
while (fsm.currentState != State.DONE) {
|
||||
def output = fsm.apply(remaining)
|
||||
if (!output) {
|
||||
throw new IllegalStateException()
|
||||
} else {
|
||||
remaining = remaining.substring(output.length())
|
||||
}
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class BooleanValue extends ComponentNode {
|
||||
boolean value
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"BooleanValue(${ this.value })"
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
abstract class ComponentNode {
|
||||
List<ComponentNode> children = []
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
abstract class ComponentNodeVisitor {
|
||||
|
||||
void visit(ComponentNode node) {
|
||||
this.visitChildren(node)
|
||||
}
|
||||
|
||||
void visitChildren(ComponentNode node) {
|
||||
node.children.each {
|
||||
this.visit(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class ComponentRoot extends ComponentNode {
|
||||
|
||||
String identifier
|
||||
String body
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"ComponentNode(identifier: ${ this.identifier }, body: ${ this.body }, children: ${ this.children })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class DollarReferenceValue extends ComponentNode {
|
||||
|
||||
String reference
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"DollarReferenceValue(${ this.reference })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class DollarScriptletValue extends ComponentNode {
|
||||
|
||||
String scriptlet
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"DollarScriptletValue(${ this.scriptlet })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class ExpressionScriptletValue extends ComponentNode {
|
||||
|
||||
String scriptlet
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"ExpressionScriptletValue(${ this.scriptlet })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class GStringValue extends ComponentNode {
|
||||
|
||||
String gString
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"GStringValue(${ this.gString })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class KeyAndValue extends ComponentNode {
|
||||
|
||||
String key
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"KeyAndValue(key: ${ this.key }, children: ${ this.children })"
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class KeysAndValues extends ComponentNode {
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"KeysAndValues(${ this.children })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class ScriptletValue extends ComponentNode {
|
||||
|
||||
String scriptlet
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"ScriptletValue(${ this.scriptlet })"
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jessebrault.gcp.component.node
|
||||
|
||||
class StringValue extends ComponentNode {
|
||||
|
||||
String string
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"StringValue(${ this.string })"
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.jessebrault.gcp.groovy
|
||||
|
||||
class DollarReferenceParser {
|
||||
|
||||
static class Result {
|
||||
String fullMatch
|
||||
String reference
|
||||
}
|
||||
|
||||
static Result parse(String input) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package com.jessebrault.gcp.groovy
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class DollarScriptletParser {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(DollarScriptletParser)
|
||||
|
||||
static class Result {
|
||||
String fullMatch
|
||||
String scriptlet
|
||||
}
|
||||
|
||||
private enum State {
|
||||
NO_STRING, G_STRING, SINGLE_QUOTE_STRING
|
||||
}
|
||||
|
||||
private static class Counter {
|
||||
|
||||
int count = 0
|
||||
|
||||
void increment() {
|
||||
this.count++
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
this.count--
|
||||
}
|
||||
|
||||
void next() {
|
||||
this.increment()
|
||||
}
|
||||
|
||||
void previous() {
|
||||
this.decrement()
|
||||
}
|
||||
|
||||
boolean isZero() {
|
||||
this.count == 0
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"Counter(${ this.count })"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static String parse(String input) {
|
||||
def acc = new StringBuilder()
|
||||
def stateStack = new LinkedList<State>([State.NO_STRING])
|
||||
def counterStack = new LinkedList<Counter>([new Counter()])
|
||||
def iterator = input.iterator() as Iterator<String>
|
||||
|
||||
if (!iterator.hasNext() || iterator.next() != '$') {
|
||||
return null
|
||||
} else {
|
||||
acc << '$'
|
||||
counterStack.peek()++
|
||||
}
|
||||
|
||||
if (!iterator.hasNext() || iterator.next() != '{') {
|
||||
return null
|
||||
} else {
|
||||
acc << '{'
|
||||
}
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
assert counterStack.size() > 0
|
||||
assert stateStack.size() > 0
|
||||
|
||||
def c0 = iterator.next()
|
||||
acc << c0
|
||||
logger.debug('----')
|
||||
logger.debug('c0: {}', c0)
|
||||
logger.debug('acc: {}', acc)
|
||||
if (stateStack.peek() == State.NO_STRING) {
|
||||
if (c0 == '{') {
|
||||
counterStack.peek()++
|
||||
} else if (c0 == '}') {
|
||||
counterStack.peek()--
|
||||
if (counterStack.peek().isZero()) {
|
||||
if (counterStack.size() == 1) {
|
||||
logger.debug('single Counter is zero; breaking while loop')
|
||||
break // escape while loop
|
||||
} else {
|
||||
logger.debug('counterStack.size() is greater than zero and top Counter is zero; ' +
|
||||
'popping state and counter stacks')
|
||||
counterStack.pop()
|
||||
stateStack.pop()
|
||||
}
|
||||
}
|
||||
} else if (c0 == '"') {
|
||||
stateStack.push(State.G_STRING)
|
||||
} else if (c0 == "'") {
|
||||
stateStack.push(State.SINGLE_QUOTE_STRING)
|
||||
}
|
||||
} else if (stateStack.peek() == State.G_STRING) {
|
||||
if (c0 == '\\') {
|
||||
if (iterator.hasNext()) {
|
||||
acc << iterator.next()
|
||||
} else {
|
||||
throw new IllegalArgumentException('Ill-formed dollar groovy')
|
||||
}
|
||||
} else if (c0 == '$') {
|
||||
if (iterator.hasNext()) {
|
||||
def c1 = iterator.next()
|
||||
acc << c1
|
||||
if (c1 == '{') {
|
||||
stateStack.push(State.NO_STRING)
|
||||
counterStack.push(new Counter())
|
||||
counterStack.peek()++
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException('Ill-formed dollar groovy')
|
||||
}
|
||||
} else if (c0 == '"') {
|
||||
logger.debug('popping G_STRING state')
|
||||
stateStack.pop()
|
||||
}
|
||||
} else if (stateStack.peek() == State.SINGLE_QUOTE_STRING) {
|
||||
if (c0 == "'") {
|
||||
logger.debug('popping SINGLE_QUOTE_STRING state')
|
||||
stateStack.pop()
|
||||
}
|
||||
}
|
||||
logger.debug('stateStack: {}', stateStack)
|
||||
logger.debug('counterStack: {}', counterStack)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class BlockScriptlet extends GcpNode {
|
||||
String scriptlet
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class ComponentInstance extends GcpNode {
|
||||
String opening
|
||||
String closing
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class Document extends GcpNode {}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class DollarReference extends GcpNode {
|
||||
String reference
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class DollarScriptlet extends GcpNode {
|
||||
String scriptlet
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class ExpressionScriptlet extends GcpNode {
|
||||
String scriptlet
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
abstract class GcpNode {
|
||||
List<GcpNode> children = []
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
abstract class GcpNodeVisitor {
|
||||
|
||||
void visit(GcpNode node) {
|
||||
this.visitChildren(node)
|
||||
}
|
||||
|
||||
void visitChildren(GcpNode node) {
|
||||
node.children.each(this.&visit)
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.node
|
||||
|
||||
class Html extends GcpNode {
|
||||
String text
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class Accumulator {
|
||||
|
||||
private static final Pattern newline = Pattern.compile("([\n\r])");
|
||||
|
||||
private final Queue<Token> tokens;
|
||||
private int inputIndex = 0;
|
||||
private int line = 1;
|
||||
private int col = 1;
|
||||
|
||||
public Accumulator(Queue<Token> tokenQueue) {
|
||||
this.tokens = tokenQueue;
|
||||
}
|
||||
|
||||
public void accumulate(Token.Type type, CharSequence text) {
|
||||
this.tokens.add(new TokenImpl(type, text, this.inputIndex, this.line, this.col));
|
||||
this.inputIndex += text.length();
|
||||
final var m = newline.matcher(text);
|
||||
if (m.find()) {
|
||||
this.line += m.groupCount();
|
||||
this.col = 1;
|
||||
} else {
|
||||
this.col += text.length();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
final class Counter {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public void increment() {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
this.count--;
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return this.count == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Counter(" + this.count + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class DollarScriptletMatcher implements FsmFunction {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DollarScriptletMatcher.class);
|
||||
|
||||
private static final class DollarScriptletMatcherOutput implements FsmOutput {
|
||||
|
||||
private final CharSequence entire;
|
||||
private final String scriptlet;
|
||||
|
||||
public DollarScriptletMatcherOutput(
|
||||
String entire,
|
||||
String scriptlet
|
||||
) {
|
||||
this.entire = entire;
|
||||
this.scriptlet = scriptlet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence entire() {
|
||||
return this.entire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence part(int index) {
|
||||
return switch (index) {
|
||||
case 1 -> "$";
|
||||
case 2 -> "{";
|
||||
case 3 -> this.scriptlet;
|
||||
case 4 -> "}";
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private enum State {
|
||||
NO_STRING, G_STRING, SINGLE_QUOTE_STRING
|
||||
}
|
||||
|
||||
private static final class CharSequenceIterator implements Iterator<String> {
|
||||
|
||||
private final CharSequence input;
|
||||
private int cur;
|
||||
|
||||
public CharSequenceIterator(CharSequence input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cur < input.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
final var c = String.valueOf(input.charAt(this.cur));
|
||||
this.cur++;
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FsmOutput apply(CharSequence s) {
|
||||
final Deque<State> stateStack = new LinkedList<>();
|
||||
final Deque<Counter> counterStack = new LinkedList<>();
|
||||
|
||||
final Supplier<Counter> currentCounterSupplier = () -> {
|
||||
final var currentCounter = counterStack.peek();
|
||||
if (currentCounter == null) {
|
||||
throw new IllegalStateException("currentCounter is null");
|
||||
}
|
||||
return currentCounter;
|
||||
};
|
||||
|
||||
stateStack.push(State.NO_STRING);
|
||||
counterStack.push(new Counter());
|
||||
|
||||
final Iterator<String> iterator = new CharSequenceIterator(s);
|
||||
|
||||
final var entireAcc = new StringBuilder();
|
||||
|
||||
if (!iterator.hasNext() || !iterator.next().equals("$")) {
|
||||
return null;
|
||||
} else {
|
||||
entireAcc.append("$");
|
||||
}
|
||||
|
||||
if (!iterator.hasNext() || !iterator.next().equals("{")) {
|
||||
return null;
|
||||
} else {
|
||||
entireAcc.append("{");
|
||||
currentCounterSupplier.get().increment();
|
||||
}
|
||||
|
||||
outer:
|
||||
while (iterator.hasNext()) {
|
||||
if (stateStack.isEmpty()) {
|
||||
throw new IllegalStateException("stateStack is empty");
|
||||
}
|
||||
if (counterStack.isEmpty()) {
|
||||
throw new IllegalStateException("counterStack is empty");
|
||||
}
|
||||
|
||||
final var c0 = iterator.next();
|
||||
entireAcc.append(c0);
|
||||
|
||||
logger.debug("----");
|
||||
logger.debug("c0: {}", c0);
|
||||
|
||||
if (stateStack.peek() == State.NO_STRING) {
|
||||
switch (c0) {
|
||||
case "{" -> currentCounterSupplier.get().increment();
|
||||
case "}" -> {
|
||||
final var currentCounter = currentCounterSupplier.get();
|
||||
currentCounter.decrement();
|
||||
if (currentCounter.isZero()) {
|
||||
if (counterStack.size() == 1) {
|
||||
logger.debug("last Counter is zero; breaking while loop");
|
||||
break outer;
|
||||
} else {
|
||||
logger.debug("counterStack.size() is greater than 1 and top Counter is zero; " +
|
||||
"popping state and counter stacks.");
|
||||
stateStack.pop();
|
||||
counterStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
case "\"" -> stateStack.push(State.G_STRING);
|
||||
case "'" -> stateStack.push(State.SINGLE_QUOTE_STRING);
|
||||
}
|
||||
} else if (stateStack.peek() == State.G_STRING) {
|
||||
switch (c0) {
|
||||
case "\\" -> {
|
||||
if (iterator.hasNext()) {
|
||||
final var c1 = iterator.next();
|
||||
entireAcc.append(c1);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Ill-formed dollarScriptlet (backslash followed by nothing)"
|
||||
);
|
||||
}
|
||||
}
|
||||
case "$" -> {
|
||||
if (iterator.hasNext()) {
|
||||
final var c1 = iterator.next();
|
||||
entireAcc.append(c1);
|
||||
if (c1.equals("{")) {
|
||||
stateStack.push(State.NO_STRING);
|
||||
counterStack.push(new Counter());
|
||||
currentCounterSupplier.get().increment();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Ill-formed dollarScriptlet (ends with a dollar)");
|
||||
}
|
||||
}
|
||||
case "\"" -> {
|
||||
logger.debug("popping G_STRING state");
|
||||
stateStack.pop();
|
||||
}
|
||||
}
|
||||
} else if (stateStack.peek() == State.SINGLE_QUOTE_STRING) {
|
||||
switch (c0) {
|
||||
case "\\" -> {
|
||||
if (iterator.hasNext()) {
|
||||
entireAcc.append(iterator.next());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Ill-formed dollarScriptlet (backslash followed by nothing)"
|
||||
);
|
||||
}
|
||||
}
|
||||
case "'" -> {
|
||||
logger.debug("popping SINGLE_QUOTE_STRING state");
|
||||
stateStack.pop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"stateStack contains something which does not equal a state or is null"
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug("entireAcc: {}", entireAcc);
|
||||
logger.debug("stateStack: {}", stateStack);
|
||||
logger.debug("counterStack: {}", counterStack);
|
||||
}
|
||||
|
||||
return new DollarScriptletMatcherOutput(
|
||||
entireAcc.toString(),
|
||||
entireAcc.substring(2, entireAcc.length() - 1)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
interface FsmFunction extends Function<CharSequence, FsmOutput> {}
|
@ -1,6 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
interface FsmOutput {
|
||||
CharSequence entire();
|
||||
CharSequence part(int index);
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import com.jessebrault.fsm.stackfunction.StackFunctionFsm;
|
||||
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilder;
|
||||
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilderImpl;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class GStringMatcher implements FsmFunction {
|
||||
|
||||
private static final class GStringMatcherOutput implements FsmOutput {
|
||||
|
||||
private final CharSequence entire;
|
||||
private final CharSequence contents;
|
||||
|
||||
public GStringMatcherOutput(String entire, String contents) {
|
||||
this.entire = entire;
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence entire() {
|
||||
return this.entire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence part(int index) {
|
||||
return switch(index) {
|
||||
case 1, 3 -> "\"";
|
||||
case 2 -> this.contents;
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final PatternMatcher text = new PatternMatcher(
|
||||
Pattern.compile("^(?:[\\w\\W&&[^$\\\\\"\\n\\r]]|\\\\[\"nrbfst\\\\u]|\\$(?!\\{|[\\w$]+(?:\\.[\\w$]+)*))+")
|
||||
);
|
||||
private static final DollarScriptletMatcher dollarScriptlet = new DollarScriptletMatcher();
|
||||
private static final PatternMatcher doubleQuote = new PatternMatcher(
|
||||
Pattern.compile("^\"")
|
||||
);
|
||||
|
||||
private enum State {
|
||||
START, CONTENTS, DONE
|
||||
}
|
||||
|
||||
private static StackFunctionFsmBuilder<CharSequence, State, FsmOutput> getFsmBuilder() {
|
||||
return new StackFunctionFsmBuilderImpl<>();
|
||||
}
|
||||
|
||||
private static StackFunctionFsm<CharSequence, State, FsmOutput> getFsm(StringBuilder acc) {
|
||||
return getFsmBuilder()
|
||||
.setInitialState(State.START)
|
||||
.whileIn(State.START, sc -> {
|
||||
sc.on(doubleQuote).shiftTo(State.CONTENTS).exec(o -> {
|
||||
acc.append(o.entire());
|
||||
});
|
||||
sc.onNoMatch().exec(input -> {
|
||||
throw new IllegalArgumentException();
|
||||
});
|
||||
})
|
||||
.whileIn(State.CONTENTS, sc -> {
|
||||
sc.on(text).exec(o -> {
|
||||
acc.append(o.entire());
|
||||
});
|
||||
sc.on(dollarScriptlet).exec(o -> {
|
||||
acc.append(o.entire());
|
||||
});
|
||||
sc.on(doubleQuote).shiftTo(State.DONE).exec(o -> {
|
||||
acc.append(o.entire());
|
||||
});
|
||||
sc.onNoMatch().exec(input -> {
|
||||
throw new IllegalArgumentException();
|
||||
});
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FsmOutput apply(final CharSequence s) {
|
||||
final var acc = new StringBuilder();
|
||||
final var fsm = getFsm(acc);
|
||||
|
||||
CharSequence remaining = s;
|
||||
|
||||
// Look-ahead
|
||||
if (!String.valueOf(remaining.charAt(0)).equals("\"")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (remaining.length() > 0) {
|
||||
final var output = fsm.apply(remaining);
|
||||
if (output == null) {
|
||||
throw new IllegalStateException("output is null");
|
||||
}
|
||||
if (fsm.getCurrentState() == State.DONE) {
|
||||
break;
|
||||
}
|
||||
remaining = remaining.subSequence(output.entire().length(), remaining.length());
|
||||
}
|
||||
|
||||
final var entire = acc.toString();
|
||||
return new GStringMatcherOutput(entire, entire.substring(1, entire.length() - 1));
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class PatternMatcher implements FsmFunction {
|
||||
|
||||
private static final class MatchResultFsmOutput implements FsmOutput {
|
||||
|
||||
private final MatchResult matchResult;
|
||||
|
||||
public MatchResultFsmOutput(MatchResult matchResult) {
|
||||
this.matchResult = matchResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence entire() {
|
||||
return this.matchResult.group(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence part(int index) {
|
||||
return this.matchResult.group(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MatchResultFsmOutput(" + this.entire() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
public PatternMatcher(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FsmOutput apply(CharSequence s) {
|
||||
final var m = this.pattern.matcher(s);
|
||||
return m.find() ? new MatchResultFsmOutput(m) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MatcherFunction(" + this.pattern + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
public final class TokenImpl implements Token {
|
||||
|
||||
private final Type type;
|
||||
private final CharSequence text;
|
||||
private final int inputIndex;
|
||||
private final int line;
|
||||
private final int col;
|
||||
|
||||
public TokenImpl(Type type, CharSequence text, int inputIndex, int line, int col) {
|
||||
this.type = type;
|
||||
this.text = text;
|
||||
this.inputIndex = inputIndex;
|
||||
this.line = line;
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Token(%s, %s, %d, %d, %d)", this.type, this.text, this.inputIndex, this.line, this.col);
|
||||
}
|
||||
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import com.jessebrault.fsm.function.FunctionFsm;
|
||||
import com.jessebrault.fsm.function.FunctionFsmBuilder;
|
||||
import com.jessebrault.fsm.function.FunctionFsmBuilderImpl;
|
||||
|
||||
import static com.jessebrault.gcp.tokenizer.Token.Type.*;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class TokenizerFsm {
|
||||
|
||||
/**
|
||||
* Text
|
||||
*/
|
||||
private static final FsmFunction text = new PatternMatcher(
|
||||
Pattern.compile("^(?:[\\w\\W&&[^<$]]|<(?!%|/?\\p{Lu}|/?[\\p{L}0-9_$]+(?:\\.[\\p{L}0-9_$]+)+)|\\$(?![\\w$]+(?:\\.[\\w$]+)*))+")
|
||||
);
|
||||
|
||||
/**
|
||||
* Gsp dollar reference and scriptlets, also used as component values
|
||||
*/
|
||||
private static final FsmFunction dollarReference = new PatternMatcher(
|
||||
Pattern.compile("^(\\$)([\\w$]+(?:\\.[\\w$]+)*)")
|
||||
);
|
||||
private static final FsmFunction dollarScriptlet = new DollarScriptletMatcher();
|
||||
private static final FsmFunction blockScriptlet = new PatternMatcher(
|
||||
Pattern.compile("^(<%)(.*?)(%>)")
|
||||
);
|
||||
private static final FsmFunction expressionScriptlet = new PatternMatcher(
|
||||
Pattern.compile("^(<%=)(.*?)(%>)")
|
||||
);
|
||||
|
||||
/**
|
||||
* Component starts
|
||||
*/
|
||||
private static final FsmFunction openingComponentStart = new PatternMatcher(
|
||||
Pattern.compile("^<(?=\\p{Lu}|[\\p{L}0-9_$]+(?:\\.[\\p{L}0-9_$]+)+)")
|
||||
);
|
||||
private static final FsmFunction closingComponentStart = new PatternMatcher(
|
||||
Pattern.compile("^(<)(/)(?=\\p{Lu}|[\\p{L}0-9_$]+(?:\\.[\\p{L}0-9_$]+)+)")
|
||||
);
|
||||
|
||||
/**
|
||||
* Component names
|
||||
*/
|
||||
private static final FsmFunction className = new PatternMatcher(
|
||||
Pattern.compile("^\\p{Lu}[\\p{L}0-9_$]*")
|
||||
);
|
||||
private static final FsmFunction packageName = new PatternMatcher(
|
||||
Pattern.compile("^[\\p{L}0-9_$]+(?=\\.)")
|
||||
);
|
||||
private static final FsmFunction dot = new PatternMatcher(
|
||||
Pattern.compile("^\\.")
|
||||
);
|
||||
|
||||
/**
|
||||
* Whitespace
|
||||
*/
|
||||
private static final FsmFunction whitespace = new PatternMatcher(Pattern.compile("^\\s+"));
|
||||
|
||||
/**
|
||||
* Keys and values
|
||||
*/
|
||||
private static final FsmFunction key = new PatternMatcher(
|
||||
Pattern.compile("^[\\p{L}0-9_$]+")
|
||||
);
|
||||
private static final FsmFunction equals = new PatternMatcher(Pattern.compile("^="));
|
||||
private static final FsmFunction singleQuoteString = new PatternMatcher(
|
||||
Pattern.compile("^(')((?:[\\w\\W&&[^\\\\'\\n\\r]]|\\\\['nrbfst\\\\u])*)(')")
|
||||
);
|
||||
private static final FsmFunction gString = new GStringMatcher();
|
||||
|
||||
/**
|
||||
* Component ends
|
||||
*/
|
||||
private static final FsmFunction forwardSlash = new PatternMatcher(Pattern.compile("^/"));
|
||||
private static final FsmFunction componentEnd = new PatternMatcher(Pattern.compile("^>"));
|
||||
|
||||
private static FunctionFsmBuilder<CharSequence, Tokenizer.State, FsmOutput> getFsmBuilder() {
|
||||
return new FunctionFsmBuilderImpl<>();
|
||||
}
|
||||
|
||||
public static FunctionFsm<CharSequence, Tokenizer.State, FsmOutput> get(Accumulator acc, Tokenizer.State state) {
|
||||
return getFsmBuilder()
|
||||
.setInitialState(state)
|
||||
.whileIn(Tokenizer.State.TEXT, sc -> {
|
||||
sc.on(text).exec(o -> {
|
||||
acc.accumulate(TEXT, o.entire());
|
||||
});
|
||||
sc.on(dollarReference).exec(o -> {
|
||||
acc.accumulate(DOLLAR, o.part(1));
|
||||
acc.accumulate(GROOVY_REFERENCE, o.part(2));
|
||||
});
|
||||
sc.on(dollarScriptlet).exec(o -> {
|
||||
acc.accumulate(DOLLAR, o.part(1));
|
||||
acc.accumulate(CURLY_OPEN, o.part(2));
|
||||
acc.accumulate(SCRIPTLET, o.part(3));
|
||||
acc.accumulate(CURLY_CLOSE, o.part(4));
|
||||
});
|
||||
sc.on(blockScriptlet).exec(o -> {
|
||||
acc.accumulate(BLOCK_SCRIPTLET_OPEN, o.part(1));
|
||||
acc.accumulate(SCRIPTLET, o.part(2));
|
||||
acc.accumulate(SCRIPTLET_CLOSE, o.part(3));
|
||||
});
|
||||
sc.on(expressionScriptlet).exec(o -> {
|
||||
acc.accumulate(EXPRESSION_SCRIPTLET_OPEN, o.part(1));
|
||||
acc.accumulate(SCRIPTLET, o.part(2));
|
||||
acc.accumulate(SCRIPTLET_CLOSE, o.part(3));
|
||||
});
|
||||
sc.on(openingComponentStart).shiftTo(Tokenizer.State.COMPONENT_NAME).exec(o ->
|
||||
acc.accumulate(COMPONENT_START, o.entire())
|
||||
);
|
||||
sc.on(closingComponentStart).shiftTo(Tokenizer.State.COMPONENT_NAME).exec(o -> {
|
||||
acc.accumulate(COMPONENT_START, o.part(1));
|
||||
acc.accumulate(FORWARD_SLASH, o.part(2));
|
||||
});
|
||||
sc.onNoMatch().exec(input -> { throw new IllegalArgumentException(); });
|
||||
})
|
||||
.whileIn(Tokenizer.State.COMPONENT_NAME, sc -> {
|
||||
sc.on(packageName).exec(o -> {
|
||||
acc.accumulate(PACKAGE_NAME, o.entire());
|
||||
});
|
||||
sc.on(dot).exec(o -> {
|
||||
acc.accumulate(DOT, o.entire());
|
||||
});
|
||||
sc.on(className).exec(o -> {
|
||||
acc.accumulate(CLASS_NAME, o.entire());
|
||||
});
|
||||
sc.on(forwardSlash).exec(o -> {
|
||||
acc.accumulate(FORWARD_SLASH, o.entire());
|
||||
});
|
||||
sc.on(componentEnd).shiftTo(Tokenizer.State.TEXT).exec(o -> {
|
||||
acc.accumulate(COMPONENT_END, o.entire());
|
||||
});
|
||||
sc.on(whitespace).shiftTo(Tokenizer.State.COMPONENT_KEYS_AND_VALUES).exec(o -> {
|
||||
acc.accumulate(WHITESPACE, o.entire());
|
||||
});
|
||||
sc.onNoMatch().exec(input -> { throw new IllegalArgumentException(); });
|
||||
})
|
||||
.whileIn(Tokenizer.State.COMPONENT_KEYS_AND_VALUES, sc -> {
|
||||
sc.on(componentEnd).shiftTo(Tokenizer.State.TEXT).exec(o -> {
|
||||
acc.accumulate(COMPONENT_END, o.entire());
|
||||
});
|
||||
sc.on(whitespace).exec(o -> {
|
||||
acc.accumulate(WHITESPACE, o.entire());
|
||||
});
|
||||
sc.on(key).exec(o -> {
|
||||
acc.accumulate(KEY, o.entire());
|
||||
});
|
||||
sc.on(equals).exec(o -> {
|
||||
acc.accumulate(EQUALS, o.entire());
|
||||
});
|
||||
sc.on(gString).exec(o -> {
|
||||
acc.accumulate(DOUBLE_QUOTE, o.part(1));
|
||||
acc.accumulate(STRING, o.part(2));
|
||||
acc.accumulate(DOUBLE_QUOTE, o.part(3));
|
||||
});
|
||||
sc.on(singleQuoteString).exec(o -> {
|
||||
acc.accumulate(SINGLE_QUOTE, o.part(1));
|
||||
acc.accumulate(STRING, o.part(2));
|
||||
acc.accumulate(SINGLE_QUOTE, o.part(3));
|
||||
});
|
||||
sc.on(dollarReference).exec(o -> {
|
||||
acc.accumulate(DOLLAR, o.part(1));
|
||||
acc.accumulate(GROOVY_REFERENCE, o.part(2));
|
||||
});
|
||||
sc.on(dollarScriptlet).exec(o -> {
|
||||
acc.accumulate(DOLLAR, o.part(1));
|
||||
acc.accumulate(CURLY_OPEN, o.part(2));
|
||||
acc.accumulate(SCRIPTLET, o.part(3));
|
||||
acc.accumulate(CURLY_CLOSE, o.part(4));
|
||||
});
|
||||
sc.on(blockScriptlet).exec(o -> {
|
||||
acc.accumulate(BLOCK_SCRIPTLET_OPEN, o.part(1));
|
||||
acc.accumulate(SCRIPTLET, o.part(2));
|
||||
acc.accumulate(SCRIPTLET_CLOSE, o.part(3));
|
||||
});
|
||||
sc.on(expressionScriptlet).exec(o -> {
|
||||
acc.accumulate(EXPRESSION_SCRIPTLET_OPEN, o.part(1));
|
||||
acc.accumulate(SCRIPTLET, o.part(2));
|
||||
acc.accumulate(SCRIPTLET_CLOSE, o.part(3));
|
||||
});
|
||||
sc.on(forwardSlash).exec(o -> {
|
||||
acc.accumulate(FORWARD_SLASH, o.entire());
|
||||
});
|
||||
sc.on(componentEnd).shiftTo(Tokenizer.State.TEXT).exec(o -> {
|
||||
acc.accumulate(COMPONENT_END, o.entire());
|
||||
});
|
||||
sc.onNoMatch().exec(input -> { throw new IllegalArgumentException(); });
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer;
|
||||
|
||||
import com.jessebrault.fsm.function.FunctionFsm;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
public final class TokenizerImpl implements Tokenizer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TokenizerImpl.class);
|
||||
|
||||
private CharSequence input;
|
||||
private int currentOffset;
|
||||
private int endOffset;
|
||||
|
||||
private Queue<Token> tokens;
|
||||
private FunctionFsm<CharSequence, State, FsmOutput> fsm;
|
||||
|
||||
@Override
|
||||
public void start(CharSequence input, int startOffset, int endOffset, State initialState) {
|
||||
this.input = input;
|
||||
this.currentOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
this.tokens = new LinkedList<>();
|
||||
this.fsm = TokenizerFsm.get(new Accumulator(this.tokens), initialState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (this.tokens.isEmpty()) {
|
||||
this.getNextTokens();
|
||||
}
|
||||
return !this.tokens.isEmpty();
|
||||
}
|
||||
|
||||
private void getNextTokens() {
|
||||
if (this.currentOffset != this.endOffset) {
|
||||
final var match = this.fsm.apply(this.input.subSequence(this.currentOffset, this.endOffset));
|
||||
if (match == null) {
|
||||
logger.error("match is null!");
|
||||
} else {
|
||||
this.currentOffset += match.entire().length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token next() {
|
||||
if (this.tokens.isEmpty()) {
|
||||
throw new IllegalStateException("currentAccumulatedTokens is empty");
|
||||
}
|
||||
return this.tokens.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getCurrentState() {
|
||||
return this.fsm.getCurrentState();
|
||||
}
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.jessebrault.gcp.util
|
||||
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
import java.util.function.Function
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
class PatternFunction implements Function<String, String> {
|
||||
|
||||
protected final Pattern pattern
|
||||
|
||||
@Override
|
||||
String apply(String s) {
|
||||
def matcher = this.pattern.matcher(s)
|
||||
matcher.find() ? matcher.group() : null
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"PatternFunction(pattern: ${ this.pattern })"
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.jessebrault.gcp.util
|
||||
|
||||
import com.jessebrault.gcp.component.ComponentToken
|
||||
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface PeekBefore {
|
||||
ComponentToken.Type[] value()
|
||||
}
|
@ -1 +0,0 @@
|
||||
com.jessebrault.gcp.GcpToScriptConverterImpl
|
@ -1,6 +0,0 @@
|
||||
def ctx = context(filetypes: ['gsp'])
|
||||
|
||||
contributor(ctx) {
|
||||
method name: 'foo', params: [bar: 'String'], type: 'int'
|
||||
property name: 'texts', type: 'java.util.List<String>', doc: 'Some texts.'
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -1,5 +0,0 @@
|
||||
def elf = foo('elf')
|
||||
|
||||
texts.each {
|
||||
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
import com.jessebrault.gcp.component.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.gcp.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 ComponentNode> {
|
||||
|
||||
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(ComponentNode 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 ComponentNode>> childSpecs = []
|
||||
|
||||
def <T extends ComponentNode> void expect(
|
||||
Class<T> childNodeClass,
|
||||
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
|
||||
@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(
|
||||
Queue<ComponentToken> tokens,
|
||||
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
|
||||
@ClosureParams(value = SimpleType, options = ['com.jessebrault.gcp.component.node.ComponentNode'])
|
||||
Closure<Void> tests
|
||||
) {
|
||||
def componentNode = this.parser.parse(tokens)
|
||||
logger.debug('componentNode: {}', componentNode)
|
||||
|
||||
def componentSpec = new NodeSpec(ComponentRoot, tests)
|
||||
logger.debug('nodeSpec: {}', componentSpec)
|
||||
componentSpec.test(componentNode)
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfClosingNoKeysOrValues() {
|
||||
this.selfClosing(new LinkedList<>([
|
||||
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 LinkedList<>([
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
package com.jessebrault.gcp.component
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
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
|
||||
|
||||
class ComponentTokenizerTests {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ComponentTokenizerTests)
|
||||
|
||||
private static class TokenSpec {
|
||||
|
||||
ComponentToken.Type type
|
||||
String text
|
||||
|
||||
TokenSpec(ComponentToken.Type type, String text = null) {
|
||||
this.type = Objects.requireNonNull(type)
|
||||
this.text = text
|
||||
}
|
||||
|
||||
void compare(ComponentToken actual) {
|
||||
assertEquals(this.type, actual.type)
|
||||
if (this.text != null) {
|
||||
assertEquals(this.text, actual.text)
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"TokenSpec(${ this.type }, ${ this.text })"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TesterConfigurator {
|
||||
|
||||
Queue<TokenSpec> specs = new LinkedList<>()
|
||||
|
||||
void expect(ComponentToken.Type type, String text) {
|
||||
this.specs << new TokenSpec(type, text)
|
||||
}
|
||||
|
||||
void expect(ComponentToken.Type type) {
|
||||
this.specs << new TokenSpec(type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final ComponentTokenizer tokenizer = new ComponentTokenizer()
|
||||
|
||||
private void test(
|
||||
String src,
|
||||
@DelegatesTo(value = TesterConfigurator, strategy = Closure.DELEGATE_FIRST)
|
||||
Closure<Void> configure
|
||||
) {
|
||||
def configurator = new TesterConfigurator()
|
||||
configure.setDelegate(configurator)
|
||||
configure.setResolveStrategy(Closure.DELEGATE_FIRST)
|
||||
configure()
|
||||
|
||||
def r = this.tokenizer.tokenize(src)
|
||||
logger.debug('r: {}', r)
|
||||
logger.debug('configurator.specs: {}', configurator.specs)
|
||||
|
||||
assertEquals(configurator.specs.size(), r.size())
|
||||
|
||||
def resultIterator = r.iterator()
|
||||
configurator.specs.each {
|
||||
assertTrue(resultIterator.hasNext())
|
||||
it.compare(resultIterator.next())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfClosingComponent() {
|
||||
this.test('<Test />') {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfClosingComponentWithDoubleQuotedString() {
|
||||
this.test('<Test key="value" />') {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect KEY, 'key'
|
||||
expect EQUALS
|
||||
expect DOUBLE_QUOTE
|
||||
expect STRING, 'value'
|
||||
expect DOUBLE_QUOTE
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void selfClosingComponentWithSingleQuotedString() {
|
||||
this.test("<Test key='value' />") {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect KEY, 'key'
|
||||
expect EQUALS
|
||||
expect SINGLE_QUOTE
|
||||
expect STRING, 'value'
|
||||
expect SINGLE_QUOTE
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithSimpleDollarGroovy() {
|
||||
this.test('<Test key=${ test } />') {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect KEY, 'key'
|
||||
expect EQUALS
|
||||
expect GROOVY, ' test '
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void dollarGroovyNestedBraces() {
|
||||
this.test('<Test key=${ test.each { it.test() } } />') {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect KEY, 'key'
|
||||
expect EQUALS
|
||||
expect GROOVY, ' test.each { it.test() } '
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void dollarReference() {
|
||||
this.test('<Test key=$test />') {
|
||||
expect LT
|
||||
expect IDENTIFIER, 'Test'
|
||||
expect KEY, 'key'
|
||||
expect EQUALS
|
||||
expect GROOVY_IDENTIFIER, 'test'
|
||||
expect FORWARD_SLASH
|
||||
expect GT
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package com.jessebrault.gcp.groovy
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static com.jessebrault.gcp.groovy.DollarScriptletParser.parse
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
class DollarScriptletParserTests {
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
assertEquals('${}', parse('${}'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void simple() {
|
||||
assertEquals('${ 1 + 2 }', parse('${ 1 + 2 }'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedString() {
|
||||
assertEquals('${ "myString" }', parse('${ "myString" }'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedCurlyBraces() {
|
||||
assertEquals(
|
||||
'${ [1, 2, 3].collect { it + 1 }.size() }',
|
||||
parse('${ [1, 2, 3].collect { it + 1 }.size() }')
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSingleQuoteString() {
|
||||
assertEquals(
|
||||
'${ \'abc\' }',
|
||||
parse('${ \'abc\' }')
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedGString() {
|
||||
assertEquals(
|
||||
'${ "abc" }',
|
||||
parse('${ "abc" }')
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedGStringWithClosure() {
|
||||
assertEquals(
|
||||
'${ "abc${ it }" }',
|
||||
parse('${ "abc${ it }" }')
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void takesOnlyAsNeeded() {
|
||||
assertEquals(
|
||||
'${ 1 + 2 }',
|
||||
parse('${ 1 + 2 } someOther=${ 3 + 4 }')
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
class DollarScriptletMatcherTests {
|
||||
|
||||
private final DollarScriptletMatcher matcher = new DollarScriptletMatcher();
|
||||
|
||||
private void test(String expectedEntire, String input) {
|
||||
def r = this.matcher.apply(input)
|
||||
assertEquals(expectedEntire, r.entire())
|
||||
assertEquals('$', r.part(1))
|
||||
assertEquals('{', r.part(2))
|
||||
assertEquals(expectedEntire.substring(2, expectedEntire.length() - 1), r.part(3))
|
||||
assertEquals('}', r.part(4))
|
||||
}
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
test '${}', '${}'
|
||||
}
|
||||
|
||||
@Test
|
||||
void simple() {
|
||||
test '${ 1 + 2 }', '${ 1 + 2 }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedString() {
|
||||
test '${ "myString" }', '${ "myString" }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedCurlyBraces() {
|
||||
test '${ [1, 2, 3].collect { it + 1 }.size() }', '${ [1, 2, 3].collect { it + 1 }.size() }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSingleQuoteString() {
|
||||
test '${ \'abc\' }', '${ \'abc\' }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedGString() {
|
||||
test '${ "abc" }', '${ "abc" }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedGStringWithClosure() {
|
||||
test '${ "abc${ it }" }', '${ "abc${ it }" }'
|
||||
}
|
||||
|
||||
@Test
|
||||
void takesOnlyAsNeeded() {
|
||||
test '${ 1 + 2 }', '${ 1 + 2 } someOther=${ 3 + 4 }'
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
class GStringMatcherTests {
|
||||
|
||||
private final GStringMatcher matcher = new GStringMatcher()
|
||||
|
||||
private void test(String expectedEntire, String input) {
|
||||
def output = this.matcher.apply(input)
|
||||
assertEquals(expectedEntire, output.entire())
|
||||
assertEquals('"', output.part(1))
|
||||
assertEquals(expectedEntire.substring(1, expectedEntire.length() - 1), output.part(2))
|
||||
assertEquals('"', output.part(3))
|
||||
}
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
test '""', '""'
|
||||
}
|
||||
|
||||
@Test
|
||||
void simple() {
|
||||
test '"abc"', '"abc"'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedDollarClosureWithGString() {
|
||||
test '"abc ${ \'def\'.each { "$it " }.join() }"', '"abc ${ \'def\'.each { "$it " }.join() }"'
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedDollarClosureWithGStringTakesOnlyAsNeeded() {
|
||||
test '"abc ${ \'def\'.each { "$it " }.join() }"', '"abc ${ \'def\'.each { "$it " }.join() }" test="rest"'
|
||||
}
|
||||
|
||||
@Test
|
||||
void takesOnlyAsNeeded() {
|
||||
test '"abc"', '"abc" test="def"'
|
||||
}
|
||||
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
package com.jessebrault.gcp.tokenizer
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import static com.jessebrault.gcp.tokenizer.Token.Type.*
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
class TokenizerTests {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TokenizerTests)
|
||||
|
||||
private static class TokenSpec {
|
||||
|
||||
Token.Type type
|
||||
String text
|
||||
int line
|
||||
int col
|
||||
|
||||
TokenSpec(Token.Type type, String text = null, line = 0, col = 0) {
|
||||
this.type = Objects.requireNonNull(type)
|
||||
this.text = text
|
||||
this.line = line
|
||||
this.col = col
|
||||
}
|
||||
|
||||
void compare(Token actual) {
|
||||
assertEquals(this.type, actual.type)
|
||||
if (this.text != null) {
|
||||
assertEquals(this.text, actual.text)
|
||||
}
|
||||
if (this.line != 0) {
|
||||
assertEquals(this.line, actual.line)
|
||||
}
|
||||
if (this.col != 0) {
|
||||
assertEquals(this.col, actual.col)
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"TokenSpec(${ this.type }, ${ this.text }, ${ this.line }, ${ this.col })"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TesterConfigurator {
|
||||
|
||||
Queue<TokenSpec> specs = new LinkedList<>()
|
||||
|
||||
void expect(Token.Type type, String text = null, line = 0, col = 0) {
|
||||
this.specs << new TokenSpec(type, text, line, col)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void test(
|
||||
String src,
|
||||
@DelegatesTo(value = TesterConfigurator, strategy = Closure.DELEGATE_FIRST)
|
||||
Closure<Void> configure
|
||||
) {
|
||||
def configurator = new TesterConfigurator()
|
||||
configure.setDelegate(configurator)
|
||||
configure.setResolveStrategy(Closure.DELEGATE_FIRST)
|
||||
configure()
|
||||
|
||||
def r = new TokenizerImpl().tokenizeAll(src, Tokenizer.State.TEXT)
|
||||
logger.debug('r: {}', r)
|
||||
logger.debug('configurator.specs: {}', configurator.specs)
|
||||
|
||||
assertEquals(configurator.specs.size(), r.size())
|
||||
|
||||
def resultIterator = r.iterator()
|
||||
configurator.specs.each {
|
||||
assertTrue(resultIterator.hasNext())
|
||||
it.compare(resultIterator.next())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void doctypeHtmlIsText() {
|
||||
test('<!DOCTYPE html>') {
|
||||
expect TEXT, '<!DOCTYPE html>', 1, 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlLangEnIsText() {
|
||||
test('<html lang="en">') {
|
||||
expect TEXT, '<html lang="en">', 1, 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void component() {
|
||||
test('<Test />') {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect CLASS_NAME, 'Test', 1, 2
|
||||
expect WHITESPACE, ' ', 1, 6
|
||||
expect FORWARD_SLASH, '/', 1, 7
|
||||
expect COMPONENT_END, '>', 1, 8
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGString() {
|
||||
test('<Test test="test" />') {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect CLASS_NAME, 'Test', 1, 2
|
||||
expect WHITESPACE, ' ', 1, 6
|
||||
expect KEY, 'test', 1, 7
|
||||
expect EQUALS, '=', 1, 11
|
||||
expect DOUBLE_QUOTE, '"', 1, 12
|
||||
expect STRING, 'test', 1, 13
|
||||
expect DOUBLE_QUOTE, '"', 1, 17
|
||||
expect WHITESPACE, ' ', 1, 18
|
||||
expect FORWARD_SLASH, '/', 1, 19
|
||||
expect COMPONENT_END, '>', 1, 20
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithGStringWithNestedGString() {
|
||||
test('<Test test="abc ${ \'abc\'.collect { "it " }.join() }" />') {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect CLASS_NAME, 'Test', 1, 2
|
||||
expect WHITESPACE, ' ', 1, 6
|
||||
expect KEY, 'test', 1, 7
|
||||
expect EQUALS, '=', 1, 11
|
||||
expect DOUBLE_QUOTE, '"', 1, 12
|
||||
expect STRING, 'abc ${ \'abc\'.collect { "it " }.join() }', 1, 13
|
||||
expect DOUBLE_QUOTE, '"', 1, 52
|
||||
expect WHITESPACE, ' ', 1, 53
|
||||
expect FORWARD_SLASH, '/', 1, 54
|
||||
expect COMPONENT_END, '>', 1, 55
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void newlinesCounted() {
|
||||
test('Hello,\n$person!') {
|
||||
expect TEXT, 'Hello,\n', 1, 1
|
||||
expect DOLLAR, '$', 2, 1
|
||||
expect GROOVY_REFERENCE, 'person', 2, 2
|
||||
expect TEXT, '!', 2, 8
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithSingleQuoteString() {
|
||||
test("<Test test='Hello, World!' />") {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect CLASS_NAME, 'Test', 1, 2
|
||||
expect WHITESPACE, ' ', 1, 6
|
||||
expect KEY, 'test', 1, 7
|
||||
expect EQUALS, '=', 1, 11
|
||||
expect SINGLE_QUOTE, "'", 1, 12
|
||||
expect STRING, 'Hello, World!', 1, 13
|
||||
expect SINGLE_QUOTE, "'", 1, 26
|
||||
expect WHITESPACE, ' ', 1, 27
|
||||
expect FORWARD_SLASH, '/', 1, 28
|
||||
expect COMPONENT_END, '>', 1, 29
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithFullyQualifiedName() {
|
||||
test('<com.jessebrault.gcp.Test />') {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect PACKAGE_NAME, 'com', 1, 2
|
||||
expect DOT, '.', 1, 5
|
||||
expect PACKAGE_NAME, 'jessebrault', 1, 6
|
||||
expect DOT, '.', 1, 17
|
||||
expect PACKAGE_NAME, 'gcp', 1, 18
|
||||
expect DOT, '.', 1, 21
|
||||
expect CLASS_NAME, 'Test', 1, 22
|
||||
expect WHITESPACE, ' ', 1, 26
|
||||
expect FORWARD_SLASH, '/', 1, 27
|
||||
expect COMPONENT_END, '>', 1, 28
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentWithNewlineWhitespace() {
|
||||
test('<Test\n/>') {
|
||||
expect COMPONENT_START, '<', 1, 1
|
||||
expect CLASS_NAME, 'Test', 1, 2
|
||||
expect WHITESPACE, '\n', 1, 6
|
||||
expect FORWARD_SLASH, '/', 2, 1
|
||||
expect COMPONENT_END, '>', 2, 2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration name="ssg" status="WARN">
|
||||
<Appenders>
|
||||
<Console name="standard" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger{1} %M %L: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger{1} %M %L: %markerSimpleName %msg%n%ex" />
|
||||
</MarkerPatternSelector>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="standard" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@ -1,2 +1,2 @@
|
||||
rootProject.name = 'ssg'
|
||||
include 'cli', 'gcp-api', 'gcp-impl', 'lib'
|
||||
include 'cli', 'lib'
|
Loading…
Reference in New Issue
Block a user