init
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
target/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
201
README.md
Normal file
201
README.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Fig
|
||||||
|
|
||||||
|
Fig is a human-readable data interchange format.
|
||||||
|
|
||||||
|
Fig must be UTF-8. All valid UTF-8 is valid Fig.
|
||||||
|
|
||||||
|
I am pretty sure Fig was an acronym, but I forgot what it stood for.
|
||||||
|
|
||||||
|
I invented Fig in a hostel in Copenhagen during KotlinConf 2019.
|
||||||
|
I was jetlagged and consuming a bunch of coffee and Carlsberg,
|
||||||
|
which may explain why I forgot what it stands for and why it is so weird.
|
||||||
|
|
||||||
|
## The format
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
|
||||||
|
Comments are in angle brackets.
|
||||||
|
|
||||||
|
```
|
||||||
|
<this is a comment>
|
||||||
|
```
|
||||||
|
|
||||||
|
You cannot have a > in a comment. Sorry.
|
||||||
|
|
||||||
|
### Data types
|
||||||
|
|
||||||
|
Fig has X data types:
|
||||||
|
|
||||||
|
* null
|
||||||
|
* boolean
|
||||||
|
* number
|
||||||
|
* string
|
||||||
|
* list
|
||||||
|
* map
|
||||||
|
|
||||||
|
### Nulls
|
||||||
|
|
||||||
|
`null` is null.
|
||||||
|
|
||||||
|
### Booleans
|
||||||
|
|
||||||
|
`true` and `false`
|
||||||
|
|
||||||
|
### Numbers
|
||||||
|
|
||||||
|
Numbers are made of an optional `+` or `-`,
|
||||||
|
followed by one or more digits (`0` - `9`),
|
||||||
|
optionally followed by `.` and one or more digits,
|
||||||
|
optionally followed by `E` then optionally a `+` or `-` then one or more digits.
|
||||||
|
|
||||||
|
There should probably be a way to indicate inifinity, negative infinity, and NaN.
|
||||||
|
|
||||||
|
### Strings
|
||||||
|
|
||||||
|
A sequence of non-whitespace characters or
|
||||||
|
a sequence of any characters quoted with `"`.
|
||||||
|
|
||||||
|
`\` escapes characters.
|
||||||
|
|
||||||
|
```
|
||||||
|
"this has a double quote in it -> \" <- right there. and a backslash here:\\"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
a"b" < <-- that is 2 strings because the " starts a new one>
|
||||||
|
```
|
||||||
|
|
||||||
|
The value here is `"a`, not `a` because there is no trailing `"`.
|
||||||
|
|
||||||
|
```
|
||||||
|
"a
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lists
|
||||||
|
|
||||||
|
A list is a sequence of values in square brackets.
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
"this is a list"
|
||||||
|
"of two strings and an integer" 9
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The closing `]` may be omitted at the end of the file.
|
||||||
|
|
||||||
|
```
|
||||||
|
[ this is a list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maps
|
||||||
|
|
||||||
|
A map is a sequence of keys and values.
|
||||||
|
|
||||||
|
Maps are enclosed in `{` and `}`.
|
||||||
|
Key/value pairs are separated by whitespace.
|
||||||
|
Keys and values are separated by `:`, with optionaly whitespace before and after the `:`.
|
||||||
|
A key with no `:` after it has a null value.
|
||||||
|
Keys are nullable strings.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
a:5
|
||||||
|
b:"hello world"
|
||||||
|
:"this value has a null key"
|
||||||
|
c:[a list value in a map]
|
||||||
|
d:{a:map in:"a map"}
|
||||||
|
e: <null value>
|
||||||
|
f <implicit null value>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The closing `}` may be omitted at the end of the file.
|
||||||
|
|
||||||
|
This is valid:
|
||||||
|
|
||||||
|
```
|
||||||
|
{ this:is a:map with:[a list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Map names
|
||||||
|
|
||||||
|
Maps can have names. This is very useful to identify a type for deserializing.
|
||||||
|
|
||||||
|
A named map has a `%` immediately after the `{`, followed by the name,
|
||||||
|
which is a sequence of non-whitespace characters.
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{%star
|
||||||
|
name:Sun
|
||||||
|
mass:1.9885E30
|
||||||
|
location:"in the middle"
|
||||||
|
}
|
||||||
|
{%planet
|
||||||
|
name:Pluto
|
||||||
|
mass:1.303E22
|
||||||
|
location:"way out there"
|
||||||
|
}
|
||||||
|
{%coment
|
||||||
|
name:"Halley's Comet"
|
||||||
|
mass:2.2E14
|
||||||
|
location:"the central part of town"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Whitespace
|
||||||
|
|
||||||
|
The following UTF-8 codepoints are considered whitespace:
|
||||||
|
|
||||||
|
* 0009 - CHARACTER TABULATION (\t)
|
||||||
|
* 000A - LINE FEED (\n)
|
||||||
|
* 000B - LINE TABULATION
|
||||||
|
* 000C - FORM FEED
|
||||||
|
* 000D - CARRIAGE RETURN (\r)
|
||||||
|
* 001C - INFORMATION SEPARATOR FOUR
|
||||||
|
* 001D - INFORMATION SEPARATOR THREE
|
||||||
|
* 001E - INFORMATION SEPARATOR TWO
|
||||||
|
* 001F - INFORMATION SEPARATOR ONE
|
||||||
|
* 0020 - SPACE
|
||||||
|
* 00A0 - NO-BREAK SPACE
|
||||||
|
* 1680 - OGHAM SPACE MARK
|
||||||
|
* 2000 - EN QUAD
|
||||||
|
* 2001 - EM QUAD
|
||||||
|
* 2002 - EN SPACE
|
||||||
|
* 2003 - EM SPACE
|
||||||
|
* 2004 - THREE-PER-EM SPACE
|
||||||
|
* 2005 - FOUR-PER-EM SPACE
|
||||||
|
* 2006 - SIX-PER-EM SPACE
|
||||||
|
* 2007 - FIGURE SPACE
|
||||||
|
* 2008 - PUNCTUATION SPACE
|
||||||
|
* 2009 - THIN SPACE
|
||||||
|
* 200A - HAIR SPACE
|
||||||
|
* 2028 - LINE SEPARATOR
|
||||||
|
* 2029 - PARAGRAPH SEPARATOR
|
||||||
|
* 202F - NARROW NO-BREAK SPACE
|
||||||
|
* 205F - MEDIUM MATHEMATICAL SPACE
|
||||||
|
* 3000 - IDEOGRAPHIC SPACE
|
||||||
|
|
||||||
|
### Fig File
|
||||||
|
|
||||||
|
A Fig file is a list or a map. If the content is not explicitly a list
|
||||||
|
(by starting with optional whitespace/comments and then a `[`)
|
||||||
|
or explicitly a map
|
||||||
|
(by starting with optional whitespace/comments and then a `{`)
|
||||||
|
then it is a list
|
||||||
|
(without a trailing `]`)
|
||||||
|
|
||||||
|
```
|
||||||
|
this is a list of 7 values <and a comment at the end>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Everything is valid?
|
||||||
|
|
||||||
|
Yup. This is where it gets weird. And where the parser probably gets slow on large files.
|
||||||
|
|
||||||
|
One advantage of making everything valid is that nobody can take advantage of undefined or
|
||||||
|
illegal syntax to make an extension of Fig.
|
||||||
|
|
||||||
|
This can almost certainly be used as evidence to either my genius or my insanity.
|
||||||
180
pom.xml
Normal file
180
pom.xml
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>net.sbyrne</groupId>
|
||||||
|
<artifactId>fig</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>${project.groupId}:${project.artifactId}</name>
|
||||||
|
<description>Fig file format</description>
|
||||||
|
<url>https://gitlab.com/sbyrne/fig</url>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>Stephen Byrne</name>
|
||||||
|
<email>gitlab@sbyrne.net</email>
|
||||||
|
<organization>Stephen Byrne</organization>
|
||||||
|
<organizationUrl>https://sbyrne.net</organizationUrl>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache License, Version 2.0</name>
|
||||||
|
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<kotlin.version>1.4.32</kotlin.version>
|
||||||
|
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<source>${project.basedir}/src/main/java</source>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<source>${project.basedir}/src/test/java</source>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.dokka</groupId>
|
||||||
|
<artifactId>dokka-maven-plugin</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>dokka</goal>
|
||||||
|
<goal>javadoc</goal>
|
||||||
|
<goal>javadocJar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
<configuration>
|
||||||
|
<useSystemClassLoader>false</useSystemClassLoader>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>enforce-no-snapshots</id>
|
||||||
|
<goals>
|
||||||
|
<goal>enforce</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<requireReleaseDeps>
|
||||||
|
<message>Cannot have snapshot dependencies of a release!</message>
|
||||||
|
<onlyWhenRelease>true</onlyWhenRelease>
|
||||||
|
<searchTransitive>true</searchTransitive>
|
||||||
|
<failWhenParentIsSnapshot>true</failWhenParentIsSnapshot>
|
||||||
|
</requireReleaseDeps>
|
||||||
|
</rules>
|
||||||
|
<fail>true</fail>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<!-- Apache 2.0 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<!-- Apache 2.0 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<!-- Apache 2.0 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.parboiled</groupId>
|
||||||
|
<artifactId>parboiled-core</artifactId>
|
||||||
|
<version>1.3.1</version>
|
||||||
|
<!-- Apache 2.0 -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.parboiled</groupId>
|
||||||
|
<artifactId>parboiled-java</artifactId>
|
||||||
|
<version>1.3.1</version>
|
||||||
|
<!-- Apache 2.0 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.module</groupId>
|
||||||
|
<artifactId>jackson-module-kotlin</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
22
src/main/java/net/sbyrne/fig/FigModel.kt
Normal file
22
src/main/java/net/sbyrne/fig/FigModel.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package net.sbyrne.fig
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
sealed class FigValue {}
|
||||||
|
|
||||||
|
data class FigList( val list:MutableList<FigValue?> = mutableListOf() ): FigValue()
|
||||||
|
|
||||||
|
data class FigMap( val name:String? = null, val map:MutableMap<String?,FigValue?> = mutableMapOf() ): FigValue()
|
||||||
|
|
||||||
|
data class FigBoolean(val value:Boolean): FigValue() {
|
||||||
|
companion object {
|
||||||
|
val TRUE = FigBoolean(true)
|
||||||
|
val FALSE = FigBoolean(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FigString( val value:String ): FigValue()
|
||||||
|
|
||||||
|
data class FigNumber(val value:BigDecimal): FigValue() {
|
||||||
|
constructor(value:String) : this(BigDecimal(value))
|
||||||
|
}
|
||||||
424
src/main/java/net/sbyrne/fig/FigParser.kt
Normal file
424
src/main/java/net/sbyrne/fig/FigParser.kt
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
package net.sbyrne.fig
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
import org.parboiled.BaseParser
|
||||||
|
import org.parboiled.Parboiled
|
||||||
|
import org.parboiled.Rule
|
||||||
|
import org.parboiled.annotations.BuildParseTree
|
||||||
|
import org.parboiled.errors.ErrorUtils
|
||||||
|
import org.parboiled.matchers.Matcher
|
||||||
|
import org.parboiled.matchers.TestMatcher
|
||||||
|
import org.parboiled.parserunners.TracingParseRunner
|
||||||
|
import org.parboiled.support.ParseTreeUtils
|
||||||
|
|
||||||
|
object FigParser {
|
||||||
|
|
||||||
|
fun parse(file: File): FigValue = parse(file.readText())
|
||||||
|
|
||||||
|
fun parse(text: String): FigValue {
|
||||||
|
println("====== parse <<<${text}>>>")
|
||||||
|
val parser = Parboiled.createParser(FigPegParser::class.java)
|
||||||
|
//val runner = ReportingParseRunner<FigValue>(parser.FigRule())
|
||||||
|
val runner = TracingParseRunner<Any?>(parser.FigRule())
|
||||||
|
val result = runner.run(text)
|
||||||
|
if (result.hasErrors()) {
|
||||||
|
throw Exception(ErrorUtils.printParseError(result.parseErrors.first()))
|
||||||
|
}
|
||||||
|
println("-----[ ${text} ]-----")
|
||||||
|
println(ParseTreeUtils.printNodeTree(result))
|
||||||
|
println("--------")
|
||||||
|
val value = result.valueStack.pop() as FigValue
|
||||||
|
if ( ! result.valueStack.isEmpty ) throw Exception( "Unexpected stuff on stack: ${result.valueStack.joinToString(",")}" )
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@BuildParseTree
|
||||||
|
open class FigPegParser: BaseParser<Any>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val WHITESPACE =
|
||||||
|
"\u0009" + // CHARACTER TABULATION (\t)
|
||||||
|
"\u000A" + // LINE FEED (\n)
|
||||||
|
"\u000B" + // LINE TABULATION
|
||||||
|
"\u000C" + // FORM FEED
|
||||||
|
"\u000D" + // CARRIAGE RETURN (\r)
|
||||||
|
"\u001C" + // INFORMATION SEPARATOR FOUR
|
||||||
|
"\u001D" + // INFORMATION SEPARATOR THREE
|
||||||
|
"\u001E" + // INFORMATION SEPARATOR TWO
|
||||||
|
"\u001F" + // INFORMATION SEPARATOR ONE
|
||||||
|
"\u0020" + // SPACE
|
||||||
|
"\u00A0" + // NO-BREAK SPACE
|
||||||
|
"\u1680" + // OGHAM SPACE MARK
|
||||||
|
"\u2000" + // EN QUAD
|
||||||
|
"\u2001" + // EM QUAD
|
||||||
|
"\u2002" + // EN SPACE
|
||||||
|
"\u2003" + // EM SPACE
|
||||||
|
"\u2004" + // THREE-PER-EM SPACE
|
||||||
|
"\u2005" + // FOUR-PER-EM SPACE
|
||||||
|
"\u2006" + // SIX-PER-EM SPACE
|
||||||
|
"\u2007" + // FIGURE SPACE
|
||||||
|
"\u2008" + // PUNCTUATION SPACE
|
||||||
|
"\u2009" + // THIN SPACE
|
||||||
|
"\u200A" + // HAIR SPACE
|
||||||
|
"\u202F" + // NARROW NO-BREAK SPACE
|
||||||
|
"\u205F" + // MEDIUM MATHEMATICAL SPACE
|
||||||
|
"\u3000" + // IDEOGRAPHIC SPACE
|
||||||
|
"\u2028" + // LINE SEPARATOR
|
||||||
|
"\u2029" // PARAGRAPH SEPARATOR
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigList or FigMap to stack
|
||||||
|
open fun FigRule(): Rule {
|
||||||
|
return FirstOf(
|
||||||
|
Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
FigListRule(), // pushes FigList
|
||||||
|
WhitespaceRule(),
|
||||||
|
EOI.skipNode()
|
||||||
|
),
|
||||||
|
Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
FigMapRule(), // pushes FigMap
|
||||||
|
WhitespaceRule(),
|
||||||
|
EOI.skipNode()
|
||||||
|
),
|
||||||
|
Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
ImpliedFigListRule(), // pushes FigList
|
||||||
|
WhitespaceRule(),
|
||||||
|
EOI.skipNode()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun WhitespaceTerminatorOrEndRule(terminators:String?): Rule {
|
||||||
|
return FirstOf(
|
||||||
|
AnyOf("${terminators?:""}${WHITESPACE}"),
|
||||||
|
CommentRule(),
|
||||||
|
EOI
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun CommentRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
"<",
|
||||||
|
ZeroOrMore(
|
||||||
|
Sequence(
|
||||||
|
TestNot( ">" ),
|
||||||
|
ANY
|
||||||
|
)
|
||||||
|
),
|
||||||
|
">"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun TestWhitespaceTerminatorOrEnd(terminators:String?): Matcher {
|
||||||
|
return TestMatcher(
|
||||||
|
WhitespaceTerminatorOrEndRule(terminators)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun WhitespaceRule(): Rule {
|
||||||
|
return ZeroOrMore(
|
||||||
|
FirstOf(
|
||||||
|
AnyOf(WHITESPACE),
|
||||||
|
CommentRule()
|
||||||
|
)
|
||||||
|
).suppressNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigList
|
||||||
|
open fun FigListRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
AnyOf("[").skipNode(),
|
||||||
|
FigListContentRule("]"), // pushes FigList
|
||||||
|
FirstOf(
|
||||||
|
AnyOf("]"),
|
||||||
|
EOI
|
||||||
|
).suppressNode()
|
||||||
|
).skipNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigList
|
||||||
|
open fun ImpliedFigListRule(): Rule {
|
||||||
|
return FigListContentRule(null) // pushes FigList
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigList
|
||||||
|
open fun FigListContentRule(terminators:String?): Rule {
|
||||||
|
return Sequence(
|
||||||
|
push(FigList()),
|
||||||
|
ZeroOrMore(
|
||||||
|
Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
ValueRule(terminators), // pushes FigValue?
|
||||||
|
addFigListValue(), // adds FigValue? on top of stack to FigList at next position on stack
|
||||||
|
WhitespaceRule()
|
||||||
|
).skipNode()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun addFigListValue(): Boolean {
|
||||||
|
( peek(1) as FigList ).list.add( pop() as FigValue? )
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigMap
|
||||||
|
open fun FigMapRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
String("{").skipNode(),
|
||||||
|
FirstOf(
|
||||||
|
Sequence(
|
||||||
|
FigMapIdentifierSegmentRule(), // pushes String
|
||||||
|
push(FigMap(name=pop() as String))
|
||||||
|
),
|
||||||
|
push(FigMap())
|
||||||
|
).skipNode(),
|
||||||
|
FigMapContentRule(), // adds k/v to map on stack
|
||||||
|
FirstOf(
|
||||||
|
String("}"),
|
||||||
|
EOI
|
||||||
|
).suppressNode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes String
|
||||||
|
open fun FigMapIdentifierSegmentRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
String("%").skipNode(),
|
||||||
|
FigMapIdentifierRule(), // pushes String
|
||||||
|
TestWhitespaceTerminatorOrEnd("}")
|
||||||
|
).skipNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes String
|
||||||
|
open fun FigMapIdentifierRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
ZeroOrMore(
|
||||||
|
Sequence(
|
||||||
|
TestNot(
|
||||||
|
WhitespaceTerminatorOrEndRule("}")
|
||||||
|
),
|
||||||
|
ANY
|
||||||
|
)
|
||||||
|
),
|
||||||
|
push(match())
|
||||||
|
).suppressSubnodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds k/v's to FigMap on stack
|
||||||
|
open fun FigMapContentRule(): Rule {
|
||||||
|
return ZeroOrMore(
|
||||||
|
Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
FigMapKeyValueRule(), // adds k/v to FigMap on stack
|
||||||
|
TestWhitespaceTerminatorOrEnd("}"),
|
||||||
|
WhitespaceRule()
|
||||||
|
).skipNode()
|
||||||
|
).skipNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds k/v to FigMap on stack
|
||||||
|
open fun FigMapKeyValueRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
FirstOf(
|
||||||
|
FigMapKeyWithValueRule(),
|
||||||
|
FigMapValueWithoutKeyRule(),
|
||||||
|
FigMapKeyWithoutValueRule()
|
||||||
|
),
|
||||||
|
addEntryToMap()
|
||||||
|
).skipNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FigMapKey(val key:FigString?)
|
||||||
|
data class FigMapValue(val value:FigValue?)
|
||||||
|
|
||||||
|
open fun addEntryToMap(): Boolean {
|
||||||
|
var key:FigMapKey? = null
|
||||||
|
var value:FigMapValue? = null
|
||||||
|
while ( peek() !is FigMap ) {
|
||||||
|
val obj = pop()
|
||||||
|
when ( obj ) {
|
||||||
|
is FigMapKey -> key = obj
|
||||||
|
is FigMapValue -> value = obj
|
||||||
|
else -> throw Exception("Unexpected ${obj}" )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
( peek() as FigMap ).map[key?.key?.value] = value?.value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun FigMapKeyWithValueRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
StringLiteralRule(":}"),
|
||||||
|
push( FigMapKey( pop() as FigString ) ),
|
||||||
|
WhitespaceRule(),
|
||||||
|
String(":").suppressNode(),
|
||||||
|
WhitespaceRule(),
|
||||||
|
Optional(
|
||||||
|
Sequence(
|
||||||
|
ValueRule("}"),
|
||||||
|
push( FigMapValue( pop() as FigValue? ) )
|
||||||
|
)
|
||||||
|
).skipNode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun FigMapValueWithoutKeyRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
WhitespaceRule(),
|
||||||
|
String(":").suppressNode(),
|
||||||
|
WhitespaceRule(),
|
||||||
|
ValueRule("}"),
|
||||||
|
push( FigMapValue( pop() as FigValue? ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun FigMapKeyWithoutValueRule(): Rule {
|
||||||
|
return Sequence(
|
||||||
|
StringLiteralRule(":}"),
|
||||||
|
push( FigMapKey( pop() as FigString ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigValue? to stack
|
||||||
|
open fun ValueRule(terminators:String?): Rule {
|
||||||
|
return FirstOf(
|
||||||
|
NullLiteralRule(terminators), // pushes null to stack
|
||||||
|
BooleanLiteralRule(terminators), // pushes FigBoolean to stack
|
||||||
|
NumericLiteralRule(terminators), // pushes FigNumber to stack
|
||||||
|
FigListRule(), // pushes FigList to stack
|
||||||
|
FigMapRule(), // pushes FigMap to stack
|
||||||
|
StringLiteralRule(terminators) // pushes FigString to map
|
||||||
|
).skipNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes null
|
||||||
|
open fun NullLiteralRule(terminators:String?): Rule {
|
||||||
|
return Sequence(
|
||||||
|
String( "null" ),
|
||||||
|
TestWhitespaceTerminatorOrEnd(terminators),
|
||||||
|
push(null)
|
||||||
|
).suppressSubnodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigBoolean
|
||||||
|
open fun BooleanLiteralRule(terminators:String?): Rule {
|
||||||
|
return Sequence(
|
||||||
|
FirstOf(
|
||||||
|
Sequence(
|
||||||
|
"true",
|
||||||
|
push( FigBoolean.TRUE )
|
||||||
|
),
|
||||||
|
Sequence(
|
||||||
|
"false",
|
||||||
|
push( FigBoolean.FALSE )
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TestWhitespaceTerminatorOrEnd(terminators)
|
||||||
|
).suppressSubnodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigNumber
|
||||||
|
open fun NumericLiteralRule(terminators:String?): Rule {
|
||||||
|
return Sequence(
|
||||||
|
Sequence(
|
||||||
|
Optional(
|
||||||
|
AnyOf( "+-" )
|
||||||
|
),
|
||||||
|
OneOrMore(DigitRule()),
|
||||||
|
Optional(
|
||||||
|
Sequence(
|
||||||
|
".",
|
||||||
|
OneOrMore(DigitRule())
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Optional(
|
||||||
|
"E",
|
||||||
|
Optional(
|
||||||
|
AnyOf("+-")
|
||||||
|
),
|
||||||
|
OneOrMore(
|
||||||
|
DigitRule()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TestWhitespaceTerminatorOrEnd(terminators)
|
||||||
|
),
|
||||||
|
push( FigNumber(match() ) )
|
||||||
|
).suppressSubnodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun DigitRule(): Rule {
|
||||||
|
return AnyOf( "0123456789" )
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes FigString
|
||||||
|
open fun StringLiteralRule( terminators:String? ): Rule {
|
||||||
|
return FirstOf(
|
||||||
|
Sequence(
|
||||||
|
"\"",
|
||||||
|
TextOrEmptyRule(charsRequiringEscape = "\""),
|
||||||
|
push( FigString( match() ) ),
|
||||||
|
"\""
|
||||||
|
),
|
||||||
|
Sequence(
|
||||||
|
TextRule(charsRequiringEscape = "${WHITESPACE}${terminators?:""}"),
|
||||||
|
push( FigString( match() ) ),
|
||||||
|
TestWhitespaceTerminatorOrEnd(terminators)
|
||||||
|
)
|
||||||
|
).suppressSubnodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A text string where the specified characters and backslash must be escaped with a backslash.
|
||||||
|
* The text can be empty.
|
||||||
|
*
|
||||||
|
* @param charsRequiringEscape
|
||||||
|
*/
|
||||||
|
open fun TextOrEmptyRule(charsRequiringEscape: String): Rule {
|
||||||
|
return ZeroOrMore(
|
||||||
|
FirstOf(
|
||||||
|
Sequence(
|
||||||
|
'\\',
|
||||||
|
AnyOf(charsRequiringEscape + '\\')
|
||||||
|
),
|
||||||
|
OneOrMore(
|
||||||
|
Sequence(
|
||||||
|
TestNot(
|
||||||
|
AnyOf(charsRequiringEscape + '\\')
|
||||||
|
),
|
||||||
|
ANY
|
||||||
|
)
|
||||||
|
).suppressSubnodes()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A text string where the specified characters and backslash must be escaped with a backslash.
|
||||||
|
* The text can be empty.
|
||||||
|
*
|
||||||
|
* @param charsRequiringEscape
|
||||||
|
*/
|
||||||
|
open fun TextRule(charsRequiringEscape: String): Rule {
|
||||||
|
return OneOrMore(
|
||||||
|
FirstOf(
|
||||||
|
Sequence(
|
||||||
|
'\\',
|
||||||
|
AnyOf(charsRequiringEscape + '\\')
|
||||||
|
),
|
||||||
|
OneOrMore(
|
||||||
|
Sequence(
|
||||||
|
TestNot(AnyOf(charsRequiringEscape + '\\')),
|
||||||
|
ANY
|
||||||
|
)
|
||||||
|
).suppressSubnodes()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
src/main/java/net/sbyrne/fig/Jackson.kt
Normal file
31
src/main/java/net/sbyrne/fig/Jackson.kt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package net.sbyrne.fig
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.databind.node.*
|
||||||
|
|
||||||
|
fun FigValue?.toJsonNode(): JsonNode =
|
||||||
|
when(this) {
|
||||||
|
null -> NullNode.instance
|
||||||
|
is FigMap -> ObjectNode(
|
||||||
|
JsonNodeFactory.instance,
|
||||||
|
map
|
||||||
|
.asSequence()
|
||||||
|
.map { it.key to it.value.toJsonNode() }
|
||||||
|
.let { m ->
|
||||||
|
if ( name != null ) {
|
||||||
|
// TODO a way to configure the attribute name. Or get rid of name?
|
||||||
|
m.plus( "@type" to TextNode(name) )
|
||||||
|
} else {
|
||||||
|
m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
)
|
||||||
|
is FigList -> ArrayNode(
|
||||||
|
JsonNodeFactory.instance,
|
||||||
|
list.map { it.toJsonNode() }
|
||||||
|
)
|
||||||
|
is FigNumber -> DecimalNode(value)
|
||||||
|
is FigString -> TextNode(value)
|
||||||
|
is FigBoolean -> if ( value ) BooleanNode.TRUE else BooleanNode.FALSE
|
||||||
|
}
|
||||||
242
src/test/java/net/sbyrne/fig/FigParserTest.kt
Normal file
242
src/test/java/net/sbyrne/fig/FigParserTest.kt
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
package net.sbyrne.fig
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class FigParserTest {
|
||||||
|
|
||||||
|
fun test(input:String,ref:FigValue?=null) {
|
||||||
|
val result = FigParser.parse(input)
|
||||||
|
println( result )
|
||||||
|
if ( ref != null ) {
|
||||||
|
assertEquals(ref,result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfNull() = test(
|
||||||
|
"[null]",
|
||||||
|
FigList(mutableListOf(null))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfNullSpaced() = test(
|
||||||
|
"[ null ]",
|
||||||
|
FigList(mutableListOf(null))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfNullAndBooleans() = test(
|
||||||
|
" [null true false null] ",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
null,
|
||||||
|
FigBoolean(true),
|
||||||
|
FigBoolean(false),
|
||||||
|
null
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun implicitListOfNull() = test(
|
||||||
|
"null",
|
||||||
|
FigList(mutableListOf(null))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun implicitListofNullSpaced() = test(
|
||||||
|
" null ",
|
||||||
|
FigList(mutableListOf(null))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun implicitListOfNullAndBooleans() = test(
|
||||||
|
" null true false null ",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
null,
|
||||||
|
FigBoolean(true),
|
||||||
|
FigBoolean(false),
|
||||||
|
null
|
||||||
|
) )
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stringStartingWithNumber() = test(
|
||||||
|
"5a",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigString("5a")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNumber() = test(
|
||||||
|
"1",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigNumber(BigDecimal("1"))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfNumbers() = test(
|
||||||
|
"[ 1 2.5 -3.5 -4 1E2 -1E2 -1.0E4 -2.3E-23 5E-2 5 ]",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigNumber("1"),
|
||||||
|
FigNumber("2.5"),
|
||||||
|
FigNumber("-3.5"),
|
||||||
|
FigNumber("-4"),
|
||||||
|
FigNumber("1E2"),
|
||||||
|
FigNumber("-1E2"),
|
||||||
|
FigNumber("-1.0E4"),
|
||||||
|
FigNumber("-2.3E-23"),
|
||||||
|
FigNumber("5E-2"),
|
||||||
|
FigNumber("5")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun escapeQuotedString() = test(
|
||||||
|
""""a\ b""",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigString("a b")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun escapeUnquotedString() = test(
|
||||||
|
"""a\ b""",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigString("a b")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfLiteralValues() = test(
|
||||||
|
"""[ true null abc "A B" x ]""",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigBoolean(true),
|
||||||
|
null,
|
||||||
|
FigString("abc"),
|
||||||
|
FigString("A B"),
|
||||||
|
FigString("x"),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listWithSublists() = test(
|
||||||
|
""" [ [ null ] abc [ "hello world" whatever ] xx] """,
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigList(mutableListOf(null)),
|
||||||
|
FigString("abc"),
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigString("hello world"),
|
||||||
|
FigString("whatever")
|
||||||
|
)),
|
||||||
|
FigString("xx")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mapWithNullKeysAndMissingValues() = test(
|
||||||
|
"""{ a:b c:d :e f }""",
|
||||||
|
FigMap(map=mutableMapOf(
|
||||||
|
"a" to FigString("b"),
|
||||||
|
"c" to FigString("d"),
|
||||||
|
null to FigString("e"),
|
||||||
|
"f" to null
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mapWithNullKeysAndNoValues() = test(
|
||||||
|
"""{ a:b c:d :e f: }""",
|
||||||
|
FigMap(map= mutableMapOf(
|
||||||
|
"a" to FigString("b"),
|
||||||
|
"c" to FigString("d"),
|
||||||
|
null to FigString("e"),
|
||||||
|
"f" to null
|
||||||
|
) )
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun namedMap() = test(
|
||||||
|
"""{%abc a:b c:d :e f: }""",
|
||||||
|
FigMap("abc", mutableMapOf(
|
||||||
|
"a" to FigString("b"),
|
||||||
|
"c" to FigString("d"),
|
||||||
|
null to FigString("e"),
|
||||||
|
"f" to null
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mapSpaces() = test(
|
||||||
|
"""{%abc a : b c : d : e f : }""",
|
||||||
|
FigMap("abc", mutableMapOf(
|
||||||
|
"a" to FigString("b"),
|
||||||
|
"c" to FigString("d"),
|
||||||
|
null to FigString("e"),
|
||||||
|
"f" to null
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun namedMapWithSubObjects() = test(
|
||||||
|
"""{%abc a:[b c] c:{%bar x:y} :e f: }""",
|
||||||
|
FigMap("abc", mutableMapOf(
|
||||||
|
"a" to FigList(mutableListOf(FigString("b"),FigString("c"))),
|
||||||
|
"c" to FigMap("bar", mutableMapOf("x" to FigString("y"))),
|
||||||
|
null to FigString("e"),
|
||||||
|
"f" to null
|
||||||
|
) )
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun trailingEnds() = test(
|
||||||
|
"""{%abc a:{ A:one B:two C:[ a ] } }""",
|
||||||
|
FigMap("abc",mutableMapOf(
|
||||||
|
"a" to FigMap(map= mutableMapOf(
|
||||||
|
"A" to FigString("one"),
|
||||||
|
"B" to FigString("two"),
|
||||||
|
"C" to FigList(mutableListOf(
|
||||||
|
FigString("a")
|
||||||
|
))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun missingTrailingEnds() = test(
|
||||||
|
"""{%abc a:{ A:one B:two C:[ a""",
|
||||||
|
FigMap("abc",mutableMapOf(
|
||||||
|
"a" to FigMap(map= mutableMapOf(
|
||||||
|
"A" to FigString("one"),
|
||||||
|
"B" to FigString("two"),
|
||||||
|
"C" to FigList(mutableListOf(
|
||||||
|
FigString("a")
|
||||||
|
))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun implicitListStartMap() = test(
|
||||||
|
"""{a:b} c""",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigMap(map= mutableMapOf("a" to FigString("b"))),
|
||||||
|
FigString("c")
|
||||||
|
) )
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun implicitListStartList() = test(
|
||||||
|
"""[a b] c""",
|
||||||
|
FigList(mutableListOf(
|
||||||
|
FigList(mutableListOf(FigString("a"),FigString("b"))),
|
||||||
|
FigString("c")
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun comments() = test("""<comm ment>{%abc<comment a>a:{<second comment>A:1 B:2 C:[ a ] } }<comment> <another comment>""")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
92
src/test/java/net/sbyrne/fig/JacksonTest.kt
Normal file
92
src/test/java/net/sbyrne/fig/JacksonTest.kt
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package net.sbyrne.fig
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||||
|
import com.fasterxml.jackson.core.Version
|
||||||
|
import com.fasterxml.jackson.databind.Module
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class JacksonTest {
|
||||||
|
|
||||||
|
val objectMapper = ObjectMapper()
|
||||||
|
.registerModule(ModelModule)
|
||||||
|
.registerKotlinModule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
val jsonNode = FigParser
|
||||||
|
.parse("""{ title:"my group" description:"blah blah blah" attendees:[ { @type:Robot id:1 } { @type:Person name:"John Doe" email:jdoe@example.com department:TECH } ]""")
|
||||||
|
.toJsonNode()
|
||||||
|
println( jsonNode )
|
||||||
|
val group = objectMapper.reader().treeToValue(jsonNode,Group::class.java)
|
||||||
|
println( group )
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testName() {
|
||||||
|
val jsonNode = FigParser
|
||||||
|
.parse("""{ title:"my group" description:"blah blah blah" attendees:[ {%Robot id:1} {%Person name:"John Doe" email:jdoe@example.com department:TECH} ]""")
|
||||||
|
.toJsonNode()
|
||||||
|
println( jsonNode )
|
||||||
|
val group = objectMapper.reader().treeToValue(jsonNode,Group::class.java)
|
||||||
|
println( group )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Group (
|
||||||
|
val title:String,
|
||||||
|
val description:String,
|
||||||
|
val attendees:List<Attendee>
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Attendee
|
||||||
|
|
||||||
|
data class Robot(
|
||||||
|
val id:Long
|
||||||
|
) : Attendee
|
||||||
|
|
||||||
|
data class Person(
|
||||||
|
val name:String,
|
||||||
|
val department:Department,
|
||||||
|
val email:String
|
||||||
|
) : Attendee
|
||||||
|
|
||||||
|
enum class Department {
|
||||||
|
TECH,
|
||||||
|
RESEARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
object ModelModule: Module() {
|
||||||
|
override fun getModuleName():String = "Kotlunch Model Module"
|
||||||
|
override fun version(): Version = Version.unknownVersion()
|
||||||
|
|
||||||
|
override fun setupModule(context:SetupContext) {
|
||||||
|
context.setMixInAnnotations( Attendee::class.java, AttendeeMixin::class.java )
|
||||||
|
context.setMixInAnnotations( Robot::class.java, RobotMixin::class.java )
|
||||||
|
context.setMixInAnnotations( Person::class.java, PersonMixin::class.java )
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeInfo(
|
||||||
|
use = JsonTypeInfo.Id.NAME,
|
||||||
|
include = JsonTypeInfo.As.PROPERTY,
|
||||||
|
property = "@type"
|
||||||
|
)
|
||||||
|
@JsonSubTypes(
|
||||||
|
JsonSubTypes.Type(value=Person::class,name="Person"),
|
||||||
|
JsonSubTypes.Type(value=Robot::class,name="Robot")
|
||||||
|
)
|
||||||
|
abstract class AttendeeMixin
|
||||||
|
|
||||||
|
class RobotMixin {
|
||||||
|
val id:Long? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class PersonMixin {
|
||||||
|
val name:String? = null
|
||||||
|
val department:Department? = null
|
||||||
|
val email:String? = null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user