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'
|
rootProject.name = 'ssg'
|
||||||
include 'cli', 'gcp-api', 'gcp-impl', 'lib'
|
include 'cli', 'lib'
|
Loading…
Reference in New Issue
Block a user