mirror of
https://github.com/plexusorg/toml4j.git
synced 2024-12-28 19:24:15 +00:00
Merge branch 'wip'
Conflicts: README.md
This commit is contained in:
commit
7e88ad0f93
56 changed files with 2395 additions and 917 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
|||
# toml4j Changelog
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Changed
|
||||
|
||||
* __BREAKING:__ Toml#getList(String) replaced Toml#getList(String, Class)
|
||||
* Dropped dependency on Parboiled and its significant transitive dependencies
|
||||
|
||||
### Added
|
||||
|
||||
* Support for [TOML 0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
* Toml#isEmpty()
|
||||
* Line numbers included in error messages
|
||||
|
||||
### Fixed
|
||||
|
||||
* Short-form Unicode escape handling
|
||||
* Exponent handling
|
||||
|
||||
## 0.3.1 / 2014-12-16
|
||||
* Support for [TOML 0.3.1](https://github.com/toml-lang/toml/tree/v0.3.1) spec
|
||||
* Pass TOML validator (https://github.com/BurntSushi/toml-test), which uncovered many bugs.
|
||||
|
|
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
Thank you for taking the time to contribute to toml4j! Here are a few guidelines to streamline the process.
|
||||
|
||||
* Cover new or modified functionality with unit tests
|
||||
* Amend README.md as necessary
|
||||
* Update the UNRELEASED section of CHANGELOG.md, as described in [keepachangelog.com](http://keepachangelog.com)
|
||||
* Use 2 spaces for indentation
|
||||
* Opening braces, parentheses, etc. are not on a new line
|
||||
|
||||
If you are unsure about how something should be implemented, open a pull request and we'll discuss it.
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Moandji Ezana
|
||||
Copyright (c) 2013-2015 Moandji Ezana
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
40
README.md
40
README.md
|
@ -1,8 +1,8 @@
|
|||
# toml4j
|
||||
|
||||
toml4j is a [TOML 0.3.1](https://github.com/toml-lang/toml/tree/v0.3.1) parser for Java.
|
||||
toml4j is a [TOML 0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) parser for Java.
|
||||
|
||||
[![Maven Central](https://img.shields.io/maven-central/v/com.moandjiezana.toml/toml4j.svg)](https://search.maven.org/#search|gav|1|g%3A%22com.moandjiezana.toml%22%20AND%20a%3A%22toml4j%22) [![Build Status](https://img.shields.io/travis/mwanji/toml4j.svg)](https://travis-ci.org/mwanji/toml4j) [![Coverage Status](https://img.shields.io/coveralls/mwanji/toml4j.svg)](https://coveralls.io/r/mwanji/toml4j)
|
||||
[![Maven Central](https://img.shields.io/maven-central/v/com.moandjiezana.toml/toml4j.svg)](https://search.maven.org/#search|gav|1|g%3A%22com.moandjiezana.toml%22%20AND%20a%3A%22toml4j%22) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Build Status](https://img.shields.io/travis/mwanji/toml4j/wip.svg)](https://travis-ci.org/mwanji/toml4j) [![Coverage Status](https://img.shields.io/coveralls/mwanji/toml4j.svg)](https://coveralls.io/r/mwanji/toml4j)
|
||||
|
||||
For the bleeding-edge version integrating the latest specs, see the [work-in-progress branch](https://github.com/mwanji/toml4j/tree/wip).
|
||||
|
||||
|
@ -14,7 +14,7 @@ Add the following dependency to your POM (or equivalent for other dependency man
|
|||
<dependency>
|
||||
<groupId>com.moandjiezana.toml</groupId>
|
||||
<artifactId>toml4j</artifactId>
|
||||
<version>0.3.1</version>
|
||||
<version>0.4.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -51,6 +51,9 @@ name = "Mwanji Ezana"
|
|||
[address]
|
||||
street = "123 A Street"
|
||||
city = "AnyVille"
|
||||
|
||||
[contacts]
|
||||
"email address" = me@example.com
|
||||
```
|
||||
|
||||
```java
|
||||
|
@ -62,6 +65,7 @@ class Address {
|
|||
class User {
|
||||
String name;
|
||||
Address address;
|
||||
Map<String, Object> contacts;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -70,18 +74,24 @@ User user = new Toml().parse(tomlFile).to(User.class);
|
|||
|
||||
assert user.name.equals("Mwanji Ezana");
|
||||
assert user.address.street.equals("123 A Street");
|
||||
assert user.contacts.get("\"email address\"").equals("me@example.com");
|
||||
```
|
||||
|
||||
Any keys not found in both the TOML and the class are ignored. Fields may be private.
|
||||
|
||||
All TOML primitives can be mapped, as well as a number of Java-specific types:
|
||||
Quoted keys cannot be mapped directly to a Java object, but they can be used as keys within a `Map`.
|
||||
|
||||
* A TOML Number can be converted to any primitive type (or the wrapper equivalent), `BigInteger` or `BigDecimal`
|
||||
* A TOML string can be converted to a `String`, enum, `java.net.URI` or `java.net.URL`
|
||||
* A single-letter TOML string can be converted to a `char` or `Character`
|
||||
* Multiline and literal TOML strings can be converted to `String`
|
||||
* A TOML array can be converted to a `List`, `Set` or array. The generic type can be anything that can be converted.
|
||||
* A TOML table can be converted to a custom class or to a `Map<String, Object>`. The generic type of the value can be anything that can be converted.
|
||||
TOML primitives can be mapped to a number of Java types:
|
||||
|
||||
TOML | Java
|
||||
---- | ----
|
||||
Integer | `int`, `long` (or wrapper), `java.math.BigInteger`
|
||||
Float | `float`, `double` (or wrapper), `java.math.BigDecimal`
|
||||
String | `String`, enum, `java.net.URI`, `java.net.URL`
|
||||
One-letter String | `char`, `Character`
|
||||
Multiline and Literal Strings | `String`
|
||||
Array | `List`, `Set`, array. The generic type can be anything that can be converted.
|
||||
Table | Custom class, `Map<String, Object>`
|
||||
|
||||
Custom classes, Maps and collections thereof can be nested to any level. See [TomlToClassTest#should_convert_fruit_table_array()](src/test/java/com/moandjiezana/toml/TomlToClassTest.java) for an example.
|
||||
|
||||
|
@ -94,7 +104,7 @@ Use the getters to retrieve the data:
|
|||
* `getBoolean(String)`
|
||||
* `getLong(String)`
|
||||
* `getDouble(String)`
|
||||
* `getList(String, Class<T>)`
|
||||
* `getList(String)`
|
||||
* `getTable(String)` returns a new Toml instance containing only the keys in that table.
|
||||
* `getTables(String)`, for table arrays, returns `List<Toml>`.
|
||||
|
||||
|
@ -102,8 +112,11 @@ You can also navigate values within a table with a compound key of the form `tab
|
|||
|
||||
Non-existent keys return null.
|
||||
|
||||
When retrieving quoted keys, the quotes must be used and the key must be spelled exactly the same way, including quotes and whitespace. The only exceptions are Unicode escapes: `"\u00B1" = "value"` would be retrieved with `toml.getString("\"±\"")`.
|
||||
|
||||
```toml
|
||||
title = "TOML Example"
|
||||
"sub title" = "Now with quoted keys"
|
||||
|
||||
[database]
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
|
@ -136,8 +149,9 @@ title = "TOML Example"
|
|||
Toml toml = new Toml().parse(getTomlFile());
|
||||
|
||||
String title = toml.getString("title");
|
||||
String subTitle = toml.getString("\"sub title\"");
|
||||
Boolean enabled = toml.getBoolean("database.enabled");
|
||||
List<Long> ports = toml.getList("database.ports", Long.class);
|
||||
List<Long> ports = toml.getList("database.ports");
|
||||
String password = toml.getString("database.credentials.password");
|
||||
|
||||
Toml servers = toml.getTable("servers");
|
||||
|
@ -192,4 +206,4 @@ Date precision is limited to milliseconds.
|
|||
|
||||
## License
|
||||
|
||||
toml4j is copyright of Moandji Ezana and is licensed under the [MIT License](LICENSE)
|
||||
toml4j is copyright (c) 2013-2015 Moandji Ezana and is licensed under the [MIT License](LICENSE)
|
||||
|
|
54
pom.xml
54
pom.xml
|
@ -11,6 +11,26 @@
|
|||
<name>toml4j</name>
|
||||
<description>A parser for TOML</description>
|
||||
<url>http://moandjiezana.com/toml/toml4j</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:mwanji/toml4j.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:mwanji/toml4j.git</developerConnection>
|
||||
<url>https://github.com/mwanji/toml4j</url>
|
||||
</scm>
|
||||
<issueManagement>
|
||||
<system>Github Issues</system>
|
||||
<url>https://github.com/mwanji/toml4j/issues</url>
|
||||
</issueManagement>
|
||||
<ciManagement>
|
||||
<system>Travis CI</system>
|
||||
<url>https://travis-ci.org/mwanji/toml4j</url>
|
||||
</ciManagement>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>moandji.ezana</id>
|
||||
|
@ -18,7 +38,8 @@
|
|||
<email>mwanji@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<inceptionYear>2013</inceptionYear>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
@ -27,18 +48,13 @@
|
|||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.parboiled</groupId>
|
||||
<artifactId>parboiled-java</artifactId>
|
||||
<version>1.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.3</version>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easytesting</groupId>
|
||||
|
@ -58,7 +74,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<version>3.2</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
|
@ -67,7 +83,7 @@
|
|||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.0.201403182114</version>
|
||||
<version>0.7.2.201409121644</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
|
@ -80,26 +96,10 @@
|
|||
<plugin>
|
||||
<groupId>org.eluder.coveralls</groupId>
|
||||
<artifactId>coveralls-maven-plugin</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<version>3.0.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:mwanji/toml4j.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:mwanji/toml4j.git</developerConnection>
|
||||
<url>https://github.com/mwanji/toml4j</url>
|
||||
</scm>
|
||||
<issueManagement>
|
||||
<system>Github Issues</system>
|
||||
<url>https://github.com/mwanji/toml4j/issues</url>
|
||||
</issueManagement>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
|
|
|
@ -1,67 +1,80 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
import static com.moandjiezana.toml.ValueConverters.CONVERTERS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class ArrayConverter implements ValueConverter {
|
||||
|
||||
static final ArrayConverter ARRAY_PARSER = new ArrayConverter();
|
||||
|
||||
private static final List<Object> INVALID_ARRAY = new ArrayList<Object>();
|
||||
private static final ValueConverters VALUE_ANALYSIS = new ValueConverters();
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
return s.startsWith("[");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
List<Object> tokens = parse(parser().Array(), s);
|
||||
List<Object> values = convertList(tokens);
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int startIndex = index.get();
|
||||
List<Object> arrayItems = new ArrayList<Object>();
|
||||
boolean terminated = false;
|
||||
boolean inComment = false;
|
||||
Results.Errors errors = new Results.Errors();
|
||||
|
||||
for (int i = index.incrementAndGet(); i < s.length(); i = index.incrementAndGet()) {
|
||||
|
||||
if (values == INVALID_ARRAY) {
|
||||
return INVALID;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<Object> convertList(List<Object> tokens) {
|
||||
ArrayList<Object> nestedList = new ArrayList<Object>();
|
||||
|
||||
for (Object token : tokens) {
|
||||
if (token instanceof String) {
|
||||
Object converted = VALUE_ANALYSIS.convert(((String) token).trim());
|
||||
if (converted == INVALID) {
|
||||
return INVALID_ARRAY;
|
||||
}
|
||||
if (isHomogenousArray(converted, nestedList)) {
|
||||
nestedList.add(converted);
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '#' && !inComment) {
|
||||
inComment = true;
|
||||
} else if (c == '\n') {
|
||||
inComment = false;
|
||||
line.incrementAndGet();
|
||||
} else if (inComment || Character.isWhitespace(c) || c == ',') {
|
||||
continue;
|
||||
} else if (c == '[') {
|
||||
Object converted = convert(s, index, context);
|
||||
if (converted instanceof Results.Errors) {
|
||||
errors.add((Results.Errors) converted);
|
||||
} else if (!isHomogenousArray(converted, arrayItems)) {
|
||||
errors.heterogenous(context.identifier.getName(), line.get());
|
||||
} else {
|
||||
return INVALID_ARRAY;
|
||||
arrayItems.add(converted);
|
||||
}
|
||||
} else if (token instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> convertedList = convertList((List<Object>) token);
|
||||
if (convertedList != INVALID_ARRAY && isHomogenousArray(convertedList, nestedList)) {
|
||||
nestedList.add(convertedList);
|
||||
continue;
|
||||
} else if (c == ']') {
|
||||
terminated = true;
|
||||
break;
|
||||
} else {
|
||||
Object converted = CONVERTERS.convert(s, index, context);
|
||||
if (converted instanceof Results.Errors) {
|
||||
errors.add((Results.Errors) converted);
|
||||
} else if (!isHomogenousArray(converted, arrayItems)) {
|
||||
errors.heterogenous(context.identifier.getName(), line.get());
|
||||
} else {
|
||||
return INVALID_ARRAY;
|
||||
arrayItems.add(converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nestedList;
|
||||
|
||||
if (!terminated) {
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex, s.length()), startLine);
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return arrayItems;
|
||||
}
|
||||
|
||||
private boolean isHomogenousArray(Object o, List<?> values) {
|
||||
return values.isEmpty() || values.get(0).getClass().isAssignableFrom(o.getClass()) || o.getClass().isAssignableFrom(values.get(0).getClass());
|
||||
}
|
||||
|
||||
|
||||
private ArrayConverter() {}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class BooleanConverter implements ValueConverter {
|
||||
|
||||
|
@ -16,14 +13,15 @@ class BooleanConverter implements ValueConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
List<String> resultValue = parse(parser().Boolean(), s);
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
s = s.substring(index.get());
|
||||
Boolean b = s.startsWith("true") ? Boolean.TRUE : Boolean.FALSE;
|
||||
|
||||
if (resultValue == null) {
|
||||
return INVALID;
|
||||
}
|
||||
int endIndex = b == Boolean.TRUE ? 4 : 5;
|
||||
|
||||
return Boolean.valueOf(resultValue.get(0));
|
||||
index.addAndGet(endIndex - 1);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
private BooleanConverter() {}
|
||||
|
|
|
@ -13,6 +13,15 @@ abstract class Container {
|
|||
|
||||
static class Table extends Container {
|
||||
private final Map<String, Object> values = new HashMap<String, Object>();
|
||||
final String name;
|
||||
|
||||
Table() {
|
||||
this.name = null;
|
||||
}
|
||||
|
||||
public Table(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean accepts(String key) {
|
||||
|
@ -68,21 +77,12 @@ abstract class Container {
|
|||
|
||||
@Override
|
||||
void put(String key, Object value) {
|
||||
if (value instanceof Container.Table) {
|
||||
values.add((Container.Table) value);
|
||||
return;
|
||||
}
|
||||
|
||||
getCurrent().put(key, value);
|
||||
values.add((Container.Table) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object get(String key) {
|
||||
if (values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getCurrent().get(key);
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> getValues() {
|
||||
|
|
19
src/main/java/com/moandjiezana/toml/Context.java
Normal file
19
src/main/java/com/moandjiezana/toml/Context.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Context {
|
||||
final Identifier identifier;
|
||||
final AtomicInteger line;
|
||||
final Results.Errors errors;
|
||||
|
||||
public Context(Identifier identifier, AtomicInteger line, Results.Errors errors) {
|
||||
this.identifier = identifier;
|
||||
this.line = line;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public Context with(Identifier identifier) {
|
||||
return new Context(identifier, line, errors);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -14,47 +12,73 @@ class DateConverter implements ValueConverter {
|
|||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
Matcher matcher = DATE_REGEX.matcher(s);
|
||||
if (s.length() < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (i < 4) {
|
||||
if (!Character.isDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
} else if (c != '-') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return matcher.matches() && ValueConverterUtils.isComment(matcher.group(4));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
public Object convert(String original, AtomicInteger index, Context context) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = index.get(); i < original.length(); i = index.incrementAndGet()) {
|
||||
char c = original.charAt(i);
|
||||
if (Character.isDigit(c) || c == '-' || c == ':' || c == '.' || c == 'T' || c == 'Z') {
|
||||
sb.append(c);
|
||||
} else {
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String s = sb.toString();
|
||||
Matcher matcher = DATE_REGEX.matcher(s);
|
||||
matcher.matches();
|
||||
s = matcher.group(1);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), s, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
String dateString = matcher.group(1);
|
||||
String zone = matcher.group(3);
|
||||
String fractionalSeconds = matcher.group(2);
|
||||
String format = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
if (fractionalSeconds != null && !fractionalSeconds.isEmpty()) {
|
||||
format += ".SSS";
|
||||
s += fractionalSeconds;
|
||||
dateString += fractionalSeconds;
|
||||
}
|
||||
format += "Z";
|
||||
if ("Z".equals(zone)) {
|
||||
s += "+0000";
|
||||
// s = s.substring(0, 22) + s.substring(23);
|
||||
dateString += "+0000";
|
||||
} else if (zone.contains(":")) {
|
||||
s += zone.replace(":", "");
|
||||
dateString += zone.replace(":", "");
|
||||
}
|
||||
|
||||
try {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
|
||||
dateFormat.setLenient(false);
|
||||
return dateFormat.parse(s);
|
||||
return dateFormat.parse(dateString);
|
||||
} catch (Exception e) {
|
||||
return INVALID;
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), s, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
private DateConverter() {}
|
||||
|
||||
public static void main(String[] args) throws ParseException {
|
||||
Pattern DATE_REGEX = Pattern.compile("(\\d{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9](?:\\.\\d*)?)(Z|(?:[+\\-]\\d{2}:\\d{2}))(.*)");
|
||||
Pattern DATE_REGEX2 = Pattern.compile("(\\d{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9])(Z|(?:[+\\-]\\d{2}:\\d{2}))");
|
||||
|
||||
System.out.println(DATE_REGEX.matcher("2011-11-11T12:32:00.999-07:00").matches());
|
||||
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").parse("1979-05-27T00:32:00+0000"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
|
||||
class ExponentConverter implements ValueConverter {
|
||||
|
||||
public static final ExponentConverter EXPONENT_PARSER = new ExponentConverter();
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
return parse(parser().Exponent(), s) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
String[] exponentString = ((String) parse(parser().Exponent(), s).get(0)).split("[eE]");
|
||||
|
||||
return Math.pow(Double.parseDouble(exponentString[0]), Double.parseDouble(exponentString[1]));
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class FloatConverter implements ValueConverter {
|
||||
|
||||
public static final FloatConverter FLOAT_PARSER = new FloatConverter();
|
||||
private static final Pattern FLOAT_REGEX = Pattern.compile("([+-]?\\d+\\.\\d+)(.*)");
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
Matcher matcher = FLOAT_REGEX.matcher(s);
|
||||
|
||||
return matcher.matches() && ValueConverterUtils.isComment(matcher.group(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
Matcher matcher = FLOAT_REGEX.matcher(s);
|
||||
matcher.matches();
|
||||
|
||||
return Double.valueOf(matcher.group(1));
|
||||
}
|
||||
|
||||
private FloatConverter() {}
|
||||
|
||||
}
|
266
src/main/java/com/moandjiezana/toml/Identifier.java
Normal file
266
src/main/java/com/moandjiezana/toml/Identifier.java
Normal file
|
@ -0,0 +1,266 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
class Identifier {
|
||||
|
||||
static final Identifier INVALID = new Identifier("", null);
|
||||
|
||||
private static final String ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-";
|
||||
|
||||
private final String name;
|
||||
private final Type type;
|
||||
|
||||
static Identifier from(String name, Context context) {
|
||||
Type type;
|
||||
boolean valid;
|
||||
name = name.trim();
|
||||
if (name.startsWith("[[")) {
|
||||
type = Type.TABLE_ARRAY;
|
||||
valid = isValidTableArray(name, context);
|
||||
} else if (name.startsWith("[")) {
|
||||
type = Type.TABLE;
|
||||
valid = isValidTable(name, context);
|
||||
} else {
|
||||
type = Type.KEY;
|
||||
valid = isValidKey(name, context);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
return Identifier.INVALID;
|
||||
}
|
||||
|
||||
return new Identifier(extractName(name), type);
|
||||
}
|
||||
|
||||
private Identifier(String name, Type type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
String getBareName() {
|
||||
if (isKey()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (isTable()) {
|
||||
return name.substring(1, name.length() - 1);
|
||||
}
|
||||
|
||||
return name.substring(2, name.length() - 2);
|
||||
}
|
||||
|
||||
boolean isKey() {
|
||||
return type == Type.KEY;
|
||||
}
|
||||
|
||||
boolean isTable() {
|
||||
return type == Type.TABLE;
|
||||
}
|
||||
|
||||
boolean isTableArray() {
|
||||
return type == Type.TABLE_ARRAY;
|
||||
}
|
||||
|
||||
private static enum Type {
|
||||
KEY, TABLE, TABLE_ARRAY;
|
||||
}
|
||||
|
||||
private static String extractName(String raw) {
|
||||
boolean quoted = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < raw.length(); i++) {
|
||||
char c = raw.charAt(i);
|
||||
if (c == '"' && (i == 0 || raw.charAt(i - 1) != '\\')) {
|
||||
quoted = !quoted;
|
||||
sb.append('"');
|
||||
} else if (quoted || !Character.isWhitespace(c)) {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return StringConverter.STRING_PARSER.replaceUnicodeCharacters(sb.toString());
|
||||
}
|
||||
|
||||
private static boolean isValidKey(String name, Context context) {
|
||||
if (name.trim().isEmpty()) {
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
|
||||
if (c == '"' && (i == 0 || name.charAt(i - 1) != '\\')) {
|
||||
if (!quoted && i > 0 && name.charAt(i - 1) != '.') {
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
quoted = !quoted;
|
||||
} else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1)) {
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidTable(String name, Context context) {
|
||||
boolean valid = true;
|
||||
|
||||
if (!name.endsWith("]")) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String trimmed = name.substring(1, name.length() - 1).trim();
|
||||
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
context.errors.invalidTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
boolean dotAllowed = false;
|
||||
boolean quoteAllowed = true;
|
||||
boolean charAllowed = true;
|
||||
|
||||
for (int i = 0; i < trimmed.length(); i++) {
|
||||
char c = trimmed.charAt(i);
|
||||
|
||||
if (!valid) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
if (!quoteAllowed) {
|
||||
valid = false;
|
||||
} else if (quoted && trimmed.charAt(i - 1) != '\\') {
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else if (!quoted) {
|
||||
quoted = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else if (quoted) {
|
||||
continue;
|
||||
} else if (c == '.') {
|
||||
if (dotAllowed) {
|
||||
charAllowed = true;
|
||||
dotAllowed = false;
|
||||
quoteAllowed = true;
|
||||
} else {
|
||||
context.errors.emptyImplicitTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
char prev = trimmed.charAt(i - 1);
|
||||
if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') {
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else {
|
||||
if (charAllowed && ALLOWED_CHARS.indexOf(c) > -1) {
|
||||
charAllowed = true;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
context.errors.invalidTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidTableArray(String line, Context context) {
|
||||
boolean valid = true;
|
||||
|
||||
if (!line.endsWith("]]")) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String trimmed = line.substring(2, line.length() - 2).trim();
|
||||
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
context.errors.invalidTableArray(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
boolean dotAllowed = false;
|
||||
boolean quoteAllowed = true;
|
||||
boolean charAllowed = true;
|
||||
|
||||
for (int i = 0; i < trimmed.length(); i++) {
|
||||
char c = trimmed.charAt(i);
|
||||
|
||||
if (!valid) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
if (!quoteAllowed) {
|
||||
valid = false;
|
||||
} else if (quoted && trimmed.charAt(i - 1) != '\\') {
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else if (!quoted) {
|
||||
quoted = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else if (quoted) {
|
||||
continue;
|
||||
} else if (c == '.') {
|
||||
if (dotAllowed) {
|
||||
charAllowed = true;
|
||||
dotAllowed = false;
|
||||
quoteAllowed = true;
|
||||
} else {
|
||||
context.errors.emptyImplicitTable(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
char prev = trimmed.charAt(i - 1);
|
||||
if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') {
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else {
|
||||
if (charAllowed && ALLOWED_CHARS.indexOf(c) > -1) {
|
||||
charAllowed = true;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
context.errors.invalidTableArray(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
62
src/main/java/com/moandjiezana/toml/IdentifierConverter.java
Normal file
62
src/main/java/com/moandjiezana/toml/IdentifierConverter.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class IdentifierConverter {
|
||||
|
||||
static final IdentifierConverter IDENTIFIER_CONVERTER = new IdentifierConverter();
|
||||
|
||||
Identifier convert(String s, AtomicInteger index, Context context) {
|
||||
boolean quoted = false;
|
||||
StringBuilder name = new StringBuilder();
|
||||
boolean terminated = false;
|
||||
boolean isKey = s.charAt(index.get()) != '[';
|
||||
boolean isTableArray = !isKey && s.length() > index.get() + 1 && s.charAt(index.get() + 1) == '[';
|
||||
boolean inComment = false;
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
if (c == '"' && (i == 0 || s.charAt(i - 1) != '\\')) {
|
||||
quoted = !quoted;
|
||||
name.append('"');
|
||||
} else if (c == '\n') {
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
} else if (quoted) {
|
||||
name.append(c);
|
||||
} else if (c == '=' && isKey) {
|
||||
terminated = true;
|
||||
break;
|
||||
} else if (c == ']' && !isKey) {
|
||||
if (!isTableArray || s.length() > index.get() + 1 && s.charAt(index.get() + 1) == ']') {
|
||||
terminated = true;
|
||||
name.append(']');
|
||||
if (isTableArray) {
|
||||
name.append(']');
|
||||
}
|
||||
}
|
||||
} else if (terminated && c == '#') {
|
||||
inComment = true;
|
||||
} else if (terminated && !Character.isWhitespace(c) && !inComment) {
|
||||
terminated = false;
|
||||
break;
|
||||
} else if (!terminated) {
|
||||
name.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
if (isKey) {
|
||||
context.errors.unterminatedKey(name.toString(), context.line.get());
|
||||
} else {
|
||||
context.errors.invalidKey(name.toString(), context.line.get());
|
||||
}
|
||||
|
||||
return Identifier.INVALID;
|
||||
}
|
||||
|
||||
return Identifier.from(name.toString(), context);
|
||||
}
|
||||
|
||||
private IdentifierConverter() {}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverters.CONVERTERS;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class InlineTableConverter implements ValueConverter {
|
||||
|
||||
static final InlineTableConverter INLINE_TABLE_PARSER = new InlineTableConverter();
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
return s.startsWith("{");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s, AtomicInteger sharedIndex, Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int startIndex = sharedIndex.get();
|
||||
boolean inKey = true;
|
||||
boolean inValue = false;
|
||||
boolean terminated = false;
|
||||
StringBuilder currentKey = new StringBuilder();
|
||||
HashMap<String, Object> results = new HashMap<String, Object>();
|
||||
Results.Errors errors = new Results.Errors();
|
||||
|
||||
for (int i = sharedIndex.incrementAndGet(); sharedIndex.get() < s.length(); i = sharedIndex.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (inValue && !Character.isWhitespace(c)) {
|
||||
Object converted = CONVERTERS.convert(s, sharedIndex, context.with(Identifier.from(currentKey.toString(), context)));
|
||||
|
||||
if (converted instanceof Results.Errors) {
|
||||
errors.add((Results.Errors) converted);
|
||||
return errors;
|
||||
}
|
||||
|
||||
String currentKeyTrimmed = currentKey.toString().trim();
|
||||
Object previous = results.put(currentKeyTrimmed, converted);
|
||||
|
||||
if (previous != null) {
|
||||
errors.duplicateKey(currentKeyTrimmed, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
currentKey = new StringBuilder();
|
||||
inValue = false;
|
||||
} else if (c == ',') {
|
||||
inKey = true;
|
||||
inValue = false;
|
||||
currentKey = new StringBuilder();
|
||||
} else if (c == '=') {
|
||||
inKey = false;
|
||||
inValue = true;
|
||||
} else if (c == '}') {
|
||||
terminated = true;
|
||||
break;
|
||||
} else if (inKey) {
|
||||
currentKey.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private InlineTableConverter() {}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class IntegerConverter implements ValueConverter {
|
||||
static final IntegerConverter INTEGER_PARSER = new IntegerConverter();
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
return parse(parser().Integer(), s) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
List<String> resultValue = parse(parser().Integer(), s);
|
||||
|
||||
String longString = resultValue.get(0);
|
||||
if (longString.startsWith("+")) {
|
||||
longString = longString.substring(1);
|
||||
}
|
||||
|
||||
return Long.valueOf(longString);
|
||||
}
|
||||
|
||||
private IntegerConverter() {}
|
||||
}
|
65
src/main/java/com/moandjiezana/toml/Keys.java
Normal file
65
src/main/java/com/moandjiezana/toml/Keys.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Keys {
|
||||
|
||||
static class Key {
|
||||
final String name;
|
||||
final int index;
|
||||
final String path;
|
||||
|
||||
Key(String name, int index, Key next) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
if (next != null) {
|
||||
this.path = name + "." + next.path;
|
||||
} else {
|
||||
this.path = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Keys.Key[] split(String key) {
|
||||
List<Key> splitKey = new ArrayList<Key>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
boolean indexable = true;
|
||||
boolean inIndex = false;
|
||||
int index = -1;
|
||||
|
||||
for (int i = key.length() - 1; i > -1; i--) {
|
||||
char c = key.charAt(i);
|
||||
if (c == ']' && indexable) {
|
||||
inIndex = true;
|
||||
continue;
|
||||
}
|
||||
indexable = false;
|
||||
if (c == '[' && inIndex) {
|
||||
inIndex = false;
|
||||
index = Integer.parseInt(current.toString());
|
||||
current = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
if (c == '"' && (i == 0 || key.charAt(i - 1) != '\\')) {
|
||||
quoted = !quoted;
|
||||
indexable = false;
|
||||
}
|
||||
if (c != '.' || quoted) {
|
||||
current.insert(0, c);
|
||||
} else {
|
||||
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
|
||||
indexable = true;
|
||||
index = -1;
|
||||
current = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
|
||||
|
||||
return splitKey.toArray(new Key[0]);
|
||||
}
|
||||
|
||||
private Keys() {}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class LiteralStringConverter implements ValueConverter {
|
||||
|
||||
|
@ -15,14 +13,29 @@ class LiteralStringConverter implements ValueConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
List<String> resultValue = parse(parser().LiteralString(), s);
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
int startLine = context.line.get();
|
||||
boolean terminated = false;
|
||||
int startIndex = index.incrementAndGet();
|
||||
|
||||
if (resultValue == null) {
|
||||
return ValueConverterUtils.INVALID;
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\'') {
|
||||
terminated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return resultValue.get(0);
|
||||
if (!terminated) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
String substring = s.substring(startIndex, index.get());
|
||||
|
||||
return substring;
|
||||
}
|
||||
|
||||
private LiteralStringConverter() {}
|
||||
|
|
|
@ -1,30 +1,9 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.parboiled.errors.ParseError;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.support.ParseTreeUtils;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class MultilineLiteralStringConverter implements ValueConverter {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ParsingResult<List<java.lang.String>> parsingResult = new RecoveringParseRunner<List<String>>(ValueConverterUtils.parser().MultilineLiteralString()).run("'''abc''' # comment");
|
||||
|
||||
if (parsingResult.hasErrors()) {
|
||||
for (ParseError parseError : parsingResult.parseErrors) {
|
||||
System.out.println(parseError.getInputBuffer().extract(0, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(ParseTreeUtils.printNodeTree(parsingResult));
|
||||
}
|
||||
|
||||
static final MultilineLiteralStringConverter MULTILINE_LITERAL_STRING_CONVERTER = new MultilineLiteralStringConverter();
|
||||
|
||||
@Override
|
||||
|
@ -33,14 +12,39 @@ class MultilineLiteralStringConverter implements ValueConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
List<String> result = parse(parser().MultilineLiteralString(), s);
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int originalStartIndex = index.get();
|
||||
int startIndex = index.addAndGet(3);
|
||||
int endIndex = -1;
|
||||
|
||||
if (result == null) {
|
||||
return INVALID;
|
||||
if (s.charAt(startIndex) == '\n') {
|
||||
startIndex = index.incrementAndGet();
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
return result.get(0);
|
||||
for (int i = startIndex; i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\n') {
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
if (c == '\'' && s.length() > i + 2 && s.charAt(i + 1) == '\'' && s.charAt(i + 2) == '\'') {
|
||||
endIndex = i;
|
||||
index.addAndGet(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex == -1) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
return s.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
private MultilineLiteralStringConverter() {}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class MultilineStringConverter implements ValueConverter {
|
||||
|
||||
|
||||
static final MultilineStringConverter MULTILINE_STRING_PARSER = new MultilineStringConverter();
|
||||
|
||||
@Override
|
||||
|
@ -12,23 +12,45 @@ class MultilineStringConverter implements ValueConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s) {
|
||||
int terminator = s.indexOf("\"\"\"", 3);
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int originalStartIndex = index.get();
|
||||
int startIndex = index.addAndGet(3);
|
||||
int endIndex = -1;
|
||||
|
||||
if (terminator == -1) {
|
||||
return INVALID;
|
||||
if (s.charAt(startIndex) == '\n') {
|
||||
startIndex = index.incrementAndGet();
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
if (!ValueConverterUtils.isComment(s.substring(terminator + 3))) {
|
||||
return INVALID;
|
||||
for (int i = startIndex; i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\n') {
|
||||
line.incrementAndGet();
|
||||
} else if (c == '"' && s.length() > i + 2 && s.charAt(i + 1) == '"' && s.charAt(i + 2) == '"') {
|
||||
endIndex = i;
|
||||
index.addAndGet(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s = s.substring(2, terminator + 1);
|
||||
if (endIndex == -1) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
s = s.substring(startIndex, endIndex);
|
||||
s = s.replaceAll("\\\\\\s+", "");
|
||||
|
||||
return StringConverter.STRING_PARSER.convert(s);
|
||||
s = StringConverter.STRING_PARSER.replaceUnicodeCharacters(s);
|
||||
s = StringConverter.STRING_PARSER.replaceSpecialCharacters(s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private MultilineStringConverter() {
|
||||
}
|
||||
|
||||
private MultilineStringConverter() {}
|
||||
|
||||
}
|
||||
|
|
78
src/main/java/com/moandjiezana/toml/NumberConverter.java
Normal file
78
src/main/java/com/moandjiezana/toml/NumberConverter.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class NumberConverter implements ValueConverter {
|
||||
static final NumberConverter NUMBER_PARSER = new NumberConverter();
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
char firstChar = s.charAt(0);
|
||||
|
||||
return firstChar == '+' || firstChar == '-' || Character.isDigit(firstChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
boolean signable = true;
|
||||
boolean dottable = false;
|
||||
boolean exponentable = false;
|
||||
boolean terminatable = false;
|
||||
String type = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (Character.isDigit(c)) {
|
||||
sb.append(c);
|
||||
signable = false;
|
||||
terminatable = true;
|
||||
if (type.isEmpty()) {
|
||||
type = "integer";
|
||||
dottable = true;
|
||||
}
|
||||
exponentable = !type.equals("exponent");
|
||||
} else if ((c == '+' || c == '-') && signable && s.length() > i + 1) {
|
||||
signable = false;
|
||||
terminatable = false;
|
||||
if (c == '-') {
|
||||
sb.append('-');
|
||||
}
|
||||
} else if (c == '.' && dottable && s.length() > i + 1) {
|
||||
sb.append('.');
|
||||
type = "float";
|
||||
terminatable = false;
|
||||
dottable = false;
|
||||
exponentable = false;
|
||||
} else if ((c == 'E' || c == 'e') && exponentable && s.length() > i + 1) {
|
||||
sb.append('E');
|
||||
type = "exponent";
|
||||
terminatable = false;
|
||||
signable = true;
|
||||
dottable = false;
|
||||
exponentable = false;
|
||||
} else {
|
||||
if (!terminatable) {
|
||||
type = "";
|
||||
}
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.equals("integer")) {
|
||||
return Long.valueOf(sb.toString());
|
||||
} else if (type.equals("float")) {
|
||||
return Double.valueOf(sb.toString());
|
||||
} else if (type.equals("exponent")) {
|
||||
String[] exponentString = sb.toString().split("E");
|
||||
|
||||
return Double.parseDouble(exponentString[0]) * Math.pow(10, Double.parseDouble(exponentString[1]));
|
||||
} else {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), sb.toString(), context.line.get());
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,35 +3,166 @@ package com.moandjiezana.toml;
|
|||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Results {
|
||||
Set<String> tables = new HashSet<String>();
|
||||
StringBuilder errors = new StringBuilder();
|
||||
private Deque<Container> stack = new ArrayDeque<Container>();
|
||||
|
||||
static class Errors {
|
||||
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
|
||||
void duplicateTable(String table, int line) {
|
||||
sb.append("Duplicate table definition on line ")
|
||||
.append(line)
|
||||
.append(": [")
|
||||
.append(table)
|
||||
.append("]\n");
|
||||
}
|
||||
|
||||
void emptyImplicitTable(String table, int line) {
|
||||
sb.append("Invalid table definition due to empty implicit table name: ")
|
||||
.append(table)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
void invalidTable(String table, int line) {
|
||||
sb.append("Invalid table definition on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(table)
|
||||
.append("]\n");
|
||||
}
|
||||
|
||||
void duplicateKey(String key, int line) {
|
||||
sb.append("Duplicate key");
|
||||
if (line > -1) {
|
||||
sb.append(" on line ")
|
||||
.append(line);
|
||||
}
|
||||
sb.append(": ")
|
||||
.append(key)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void invalidTextAfterIdentifier(Identifier identifier, char text, int line) {
|
||||
sb.append("Invalid text after key ")
|
||||
.append(identifier.getName())
|
||||
.append(" on line ")
|
||||
.append(line)
|
||||
.append(". Make sure to terminate the value or add a comment (#).")
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void invalidKey(String key, int line) {
|
||||
sb.append("Invalid key on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void invalidTableArray(String tableArray, int line) {
|
||||
sb.append("Invalid table array definition on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(tableArray)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void invalidValue(String key, String value, int line) {
|
||||
sb.append("Invalid value on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append(" = ")
|
||||
.append(value)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void unterminatedKey(String key, int line) {
|
||||
sb.append("Key is not followed by an equals sign on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
void unterminated(String key, String value, int line) {
|
||||
sb.append("Unterminated value on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append(" = ")
|
||||
.append(value.trim())
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
public void heterogenous(String key, int line) {
|
||||
sb.append(key)
|
||||
.append(" becomes a heterogeneous array on line ")
|
||||
.append(line)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
boolean hasErrors() {
|
||||
return sb.length() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void add(Errors other) {
|
||||
sb.append(other.sb);
|
||||
}
|
||||
}
|
||||
|
||||
final Errors errors = new Errors();
|
||||
private final Set<String> tables = new HashSet<String>();
|
||||
private final Deque<Container> stack = new ArrayDeque<Container>();
|
||||
|
||||
Results() {
|
||||
stack.push(new Container.Table());
|
||||
stack.push(new Container.Table(""));
|
||||
}
|
||||
|
||||
void addValue(String key, Object value) {
|
||||
void addValue(String key, Object value, AtomicInteger line) {
|
||||
Container currentTable = stack.peek();
|
||||
if (currentTable.accepts(key)) {
|
||||
|
||||
if (value instanceof Map) {
|
||||
String path = getInlineTablePath(key);
|
||||
if (path == null) {
|
||||
startTable(key, line);
|
||||
} else if (path.isEmpty()) {
|
||||
startTables(Identifier.from(key, null), line);
|
||||
} else {
|
||||
startTables(Identifier.from(path, null), line);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> valueMap = (Map<String, Object>) value;
|
||||
for (Map.Entry<String, Object> entry : valueMap.entrySet()) {
|
||||
addValue(entry.getKey(), entry.getValue(), line);
|
||||
}
|
||||
stack.pop();
|
||||
} else if (currentTable.accepts(key)) {
|
||||
currentTable.put(key, value);
|
||||
} else {
|
||||
errors.append("Key " + key + " is defined twice!\n");
|
||||
errors.duplicateKey(key, line != null ? line.get() : -1);
|
||||
}
|
||||
}
|
||||
|
||||
void startTableArray(String tableName) {
|
||||
void startTableArray(Identifier identifier, AtomicInteger line) {
|
||||
String tableName = identifier.getBareName();
|
||||
while (stack.size() > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
String[] tableParts = tableName.split("\\.");
|
||||
Keys.Key[] tableParts = Keys.split(tableName);
|
||||
for (int i = 0; i < tableParts.length; i++) {
|
||||
String tablePart = tableParts[i];
|
||||
String tablePart = tableParts[i].name;
|
||||
Container currentContainer = stack.peek();
|
||||
|
||||
if (currentContainer.get(tablePart) instanceof Container.TableArray) {
|
||||
|
@ -49,48 +180,43 @@ class Results {
|
|||
stack.push(nextTable);
|
||||
} else if (currentContainer.accepts(tablePart)) {
|
||||
Container newContainer = i == tableParts.length - 1 ? new Container.TableArray() : new Container.Table();
|
||||
addValue(tablePart, newContainer);
|
||||
addValue(tablePart, newContainer, line);
|
||||
stack.push(newContainer);
|
||||
|
||||
if (newContainer instanceof Container.TableArray) {
|
||||
stack.push(((Container.TableArray) newContainer).getCurrent());
|
||||
}
|
||||
} else {
|
||||
errors.append("Duplicate key and table definitions for " + tableName + "!\n");
|
||||
errors.duplicateTable(tableName, line.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startTables(String tableName) {
|
||||
void startTables(Identifier id, AtomicInteger line) {
|
||||
String tableName = id.getBareName();
|
||||
if (!tables.add(tableName)) {
|
||||
errors.append("Table " + tableName + " defined twice!\n");
|
||||
errors.duplicateTable(tableName, line.get());
|
||||
}
|
||||
|
||||
if (tableName.endsWith(".")) {
|
||||
errors.append("Implicit table name cannot be empty: " + tableName);
|
||||
}
|
||||
|
||||
while (stack.size() > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
String[] tableParts = tableName.split("\\.");
|
||||
Keys.Key[] tableParts = Keys.split(tableName);
|
||||
for (int i = 0; i < tableParts.length; i++) {
|
||||
String tablePart = tableParts[i];
|
||||
String tablePart = tableParts[i].name;
|
||||
Container currentContainer = stack.peek();
|
||||
if (tablePart.isEmpty()) {
|
||||
errors.append("Empty implicit table: " + tableName + "!\n");
|
||||
} else if (currentContainer.get(tablePart) instanceof Container) {
|
||||
if (currentContainer.get(tablePart) instanceof Container) {
|
||||
Container nextTable = (Container) currentContainer.get(tablePart);
|
||||
stack.push(nextTable);
|
||||
if (stack.peek() instanceof Container.TableArray) {
|
||||
stack.push(((Container.TableArray) stack.peek()).getCurrent());
|
||||
}
|
||||
} else if (currentContainer.accepts(tablePart)) {
|
||||
startTable(tablePart);
|
||||
startTable(tablePart, line);
|
||||
} else {
|
||||
errors.append("Duplicate key and table definitions for " + tableName + "!\n");
|
||||
errors.duplicateTable(tableName, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -106,11 +232,45 @@ class Results {
|
|||
return ((Container.Table) values).consume();
|
||||
}
|
||||
|
||||
private Container startTable(String tableName) {
|
||||
Container newTable = new Container.Table();
|
||||
addValue(tableName, newTable);
|
||||
private Container startTable(String tableName, AtomicInteger line) {
|
||||
Container newTable = new Container.Table(tableName);
|
||||
addValue(tableName, newTable, line);
|
||||
stack.push(newTable);
|
||||
|
||||
return newTable;
|
||||
}
|
||||
|
||||
private String getInlineTablePath(String key) {
|
||||
Iterator<Container> descendingIterator = stack.descendingIterator();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (descendingIterator.hasNext()) {
|
||||
Container next = descendingIterator.next();
|
||||
if (next instanceof Container.TableArray) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Container.Table table = (Container.Table) next;
|
||||
|
||||
if (table.name == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.append('.');
|
||||
}
|
||||
|
||||
sb.append(table.name);
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.append('.');
|
||||
}
|
||||
|
||||
sb.append(key)
|
||||
.insert(0, '[')
|
||||
.append(']');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class StringConverter implements ValueConverter {
|
||||
|
||||
static final StringConverter STRING_PARSER = new StringConverter();
|
||||
private static final Pattern UNICODE_REGEX = Pattern.compile("\\\\[uU](.*)");
|
||||
private static final Pattern UNICODE_REGEX = Pattern.compile("\\\\[uU](.{4})");
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
|
@ -17,43 +15,38 @@ class StringConverter implements ValueConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convert(String value) {
|
||||
int stringTerminator = -1;
|
||||
char[] chars = value.toCharArray();
|
||||
public Object convert(String s, AtomicInteger index, Context context) {
|
||||
int startIndex = index.incrementAndGet();
|
||||
int endIndex = -1;
|
||||
|
||||
for (int i = 1; i < chars.length; i++) {
|
||||
char ch = chars[i];
|
||||
if (ch == '"' && chars[i - 1] != '\\') {
|
||||
stringTerminator = i;
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch == '"' && s.charAt(i - 1) != '\\') {
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stringTerminator == -1 || !isComment(value.substring(stringTerminator + 1))) {
|
||||
return INVALID;
|
||||
if (endIndex == -1) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex - 1), context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
value = value.substring(1, stringTerminator);
|
||||
value = replaceUnicodeCharacters(value);
|
||||
|
||||
chars = value.toCharArray();
|
||||
for (int i = 0; i < chars.length - 1; i++) {
|
||||
char ch = chars[i];
|
||||
char next = chars[i + 1];
|
||||
|
||||
if (ch == '\\' && next == '\\') {
|
||||
i++;
|
||||
} else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '/' || next == '\\')) {
|
||||
return INVALID;
|
||||
}
|
||||
String raw = s.substring(startIndex, endIndex);
|
||||
s = replaceUnicodeCharacters(raw);
|
||||
s = replaceSpecialCharacters(s);
|
||||
|
||||
if (s == null) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), raw, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
value = replaceSpecialCharacters(value);
|
||||
|
||||
return value;
|
||||
return s;
|
||||
}
|
||||
|
||||
private String replaceUnicodeCharacters(String value) {
|
||||
String replaceUnicodeCharacters(String value) {
|
||||
Matcher unicodeMatcher = UNICODE_REGEX.matcher(value);
|
||||
|
||||
while (unicodeMatcher.find()) {
|
||||
|
@ -62,8 +55,19 @@ class StringConverter implements ValueConverter {
|
|||
return value;
|
||||
}
|
||||
|
||||
private String replaceSpecialCharacters(String value) {
|
||||
return value.replace("\\n", "\n")
|
||||
String replaceSpecialCharacters(String s) {
|
||||
for (int i = 0; i < s.length() - 1; i++) {
|
||||
char ch = s.charAt(i);
|
||||
char next = s.charAt(i + 1);
|
||||
|
||||
if (ch == '\\' && next == '\\') {
|
||||
i++;
|
||||
} else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '\\')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return s.replace("\\n", "\n")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\t", "\t")
|
||||
.replace("\\r", "\r")
|
||||
|
|
|
@ -8,19 +8,13 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
@ -28,9 +22,11 @@ import com.google.gson.JsonElement;
|
|||
/**
|
||||
* <p>Provides access to the keys and tables in a TOML data source.</p>
|
||||
*
|
||||
* <p>All getters can fall back to default values if they have been provided.
|
||||
* <p>All getters can fall back to default values if they have been provided as a constructor argument.
|
||||
* Getters for simple values (String, Date, etc.) will return null if no matching key exists.
|
||||
* {@link #getList(String, Class)}, {@link #getTable(String)} and {@link #getTables(String)} return empty values if there is no matching key.</p>
|
||||
* {@link #getList(String)}, {@link #getTable(String)} and {@link #getTables(String)} return empty values if there is no matching key.</p>
|
||||
*
|
||||
* <p>All parse methods throw an {@link IllegalStateException} if the TOML is incorrect.</p>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
* <pre><code>
|
||||
|
@ -45,7 +41,6 @@ import com.google.gson.JsonElement;
|
|||
public class Toml {
|
||||
|
||||
private static final Gson DEFAULT_GSON = new Gson();
|
||||
private static final Pattern ARRAY_INDEX_PATTERN = Pattern.compile("(.*)\\[(\\d+)\\]");
|
||||
|
||||
private Map<String, Object> values = new HashMap<String, Object>();
|
||||
private final Toml defaults;
|
||||
|
@ -127,8 +122,8 @@ public class Toml {
|
|||
* @throws IllegalStateException If tomlString is not valid TOML
|
||||
*/
|
||||
public Toml parse(String tomlString) throws IllegalStateException {
|
||||
Results results = new TomlParser().run(tomlString);
|
||||
if (results.errors.length() > 0) {
|
||||
Results results = TomlParser.run(tomlString);
|
||||
if (results.errors.hasErrors()) {
|
||||
throw new IllegalStateException(results.errors.toString());
|
||||
}
|
||||
|
||||
|
@ -145,8 +140,13 @@ public class Toml {
|
|||
return (Long) get(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getList(String key, Class<T> itemClass) {
|
||||
/**
|
||||
* @param key a TOML key
|
||||
* @param <T> type of list items
|
||||
* @return an empty {@link List} is the key is not found
|
||||
*/
|
||||
public <T> List<T> getList(String key) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<T> list = (List<T>) get(key);
|
||||
|
||||
if (list == null) {
|
||||
|
@ -179,7 +179,7 @@ public class Toml {
|
|||
|
||||
/**
|
||||
* @param key Name of array of tables, not including square brackets.
|
||||
* @return An empty List if no value is found for key.
|
||||
* @return An empty {@link List} if no value is found for key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Toml> getTables(String key) {
|
||||
|
@ -198,19 +198,29 @@ public class Toml {
|
|||
return tables;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return values.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Populates an instance of targetClass with the values of this Toml instance.
|
||||
* The target's field names must match keys or tables.
|
||||
* Keys not present in targetClass will be ignored.</p>
|
||||
* <p>
|
||||
* Populates an instance of targetClass with the values of this Toml instance.
|
||||
* The target's field names must match keys or tables.
|
||||
* Keys not present in targetClass will be ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>Tables are recursively converted to custom classes or to {@link Map Map<String, Object>}.</p>
|
||||
*
|
||||
* <p>In addition to straight-forward conversion of TOML primitives, the following are also available:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>TOML string to {@link Character}, {@link URL} or enum</li>
|
||||
* <li>TOML number to any primitive (or wrapper), {@link BigInteger} or {@link BigDecimal}</li>
|
||||
* <li>TOML array to {@link Set}</li>
|
||||
* <li>Integer -> int, long (or wrapper), {@link java.math.BigInteger}</li>
|
||||
* <li>Float -> float, double (or wrapper), {@link java.math.BigDecimal}</li>
|
||||
* <li>One-letter String -> char, {@link Character}</li>
|
||||
* <li>String -> {@link String}, enum, {@link java.net.URI}, {@link java.net.URL}</li>
|
||||
* <li>Multiline and Literal Strings -> {@link String}</li>
|
||||
* <li>Array -> {@link List}, {@link Set}, array. The generic type can be anything that can be converted.</li>
|
||||
* <li>Table -> Custom class, {@link Map Map<String, Object>}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param targetClass Class to deserialize TOML to.
|
||||
|
@ -246,55 +256,32 @@ public class Toml {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object get(String key) {
|
||||
String[] split = key.split("\\.");
|
||||
if (values.containsKey(key)) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
Object current = new HashMap<String, Object>(values);
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
if (i == 0 && values.containsKey(key)) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
String keyWithDot = join(Arrays.copyOfRange(split, i, split.length));
|
||||
if (current instanceof Map && ((Map<String, Object>) current).containsKey(keyWithDot)) {
|
||||
return ((Map<String, Object>) current).get(keyWithDot);
|
||||
}
|
||||
|
||||
String splitKey = split[i];
|
||||
Matcher matcher = ARRAY_INDEX_PATTERN.matcher(splitKey);
|
||||
int index = -1;
|
||||
|
||||
if (matcher.find()) {
|
||||
splitKey = matcher.group(1);
|
||||
index = Integer.parseInt(matcher.group(2), 10);
|
||||
Keys.Key[] keys = Keys.split(key);
|
||||
|
||||
for (Keys.Key k : keys) {
|
||||
if (k.index == -1 && current instanceof Map && ((Map<String, Object>) current).containsKey(k.path)) {
|
||||
return ((Map<String, Object>) current).get(k.path);
|
||||
}
|
||||
|
||||
current = ((Map<String, Object>) current).get(splitKey);
|
||||
current = ((Map<String, Object>) current).get(k.name);
|
||||
|
||||
if (index > -1 && current != null) {
|
||||
current = ((List<?>) current).get(index);
|
||||
if (k.index > -1 && current != null) {
|
||||
current = ((List<?>) current).get(k.index);
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
return defaults != null ? defaults.get(key) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private String join(String[] strings) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String string : strings) {
|
||||
sb.append(string).append('.');
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private Toml(Toml defaults, Map<String, Object> values) {
|
||||
this.values = values != null ? values : Collections.<String, Object>emptyMap();
|
||||
|
|
|
@ -1,251 +1,65 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parse;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.parser;
|
||||
import static com.moandjiezana.toml.IdentifierConverter.IDENTIFIER_CONVERTER;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class TomlParser {
|
||||
private static final String STRING_LITERAL_DELIMITER = "'''";
|
||||
private static final Pattern MULTILINE_ARRAY_REGEX = Pattern.compile("\\s*\\[([^\\]]*)");
|
||||
private static final Pattern MULTILINE_ARRAY_REGEX_END = Pattern.compile("\\s*\\]");
|
||||
private static final ValueConverters VALUE_ANALYSIS = new ValueConverters();
|
||||
|
||||
private final Results results = new Results();
|
||||
|
||||
Results run(String tomlString) {
|
||||
static Results run(String tomlString) {
|
||||
final Results results = new Results();
|
||||
|
||||
if (tomlString.isEmpty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
String[] lines = tomlString.split("[\\n\\r]");
|
||||
StringBuilder multilineBuilder = new StringBuilder();
|
||||
Multiline multiline = Multiline.NONE;
|
||||
|
||||
String key = null;
|
||||
String value = null;
|
||||
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
|
||||
if (line != null && multiline.isTrimmable()) {
|
||||
line = line.trim();
|
||||
}
|
||||
|
||||
if (isComment(line) || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isTableArray(line)) {
|
||||
String tableName = getTableArrayName(line);
|
||||
if (tableName != null) {
|
||||
results.startTableArray(tableName);
|
||||
String afterTableName = line.substring(tableName.length() + 4);
|
||||
if (!isComment(afterTableName)) {
|
||||
results.errors.append("Invalid table array definition: " + line + "\n\n");
|
||||
}
|
||||
} else {
|
||||
results.errors.append("Invalid table array definition: " + line + "\n\n");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multiline.isNotMultiline() && isTable(line)) {
|
||||
String tableName = getTableName(line);
|
||||
if (tableName != null) {
|
||||
results.startTables(tableName);
|
||||
} else {
|
||||
results.errors.append("Invalid table definition: " + line + "\n\n");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multiline.isNotMultiline() && !line.contains("=")) {
|
||||
results.errors.append("Invalid key definition: " + line);
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] pair = line.split("=", 2);
|
||||
|
||||
if (multiline.isNotMultiline() && MULTILINE_ARRAY_REGEX.matcher(pair[1].trim()).matches()) {
|
||||
multiline = Multiline.ARRAY;
|
||||
key = pair[0].trim();
|
||||
multilineBuilder.append(removeComment(pair[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multiline.isNotMultiline() && pair[1].trim().startsWith("\"\"\"")) {
|
||||
multiline = Multiline.STRING;
|
||||
multilineBuilder.append(pair[1]);
|
||||
key = pair[0].trim();
|
||||
|
||||
if (pair[1].trim().indexOf("\"\"\"", 3) > -1) {
|
||||
multiline = Multiline.NONE;
|
||||
pair[1] = multilineBuilder.toString().trim();
|
||||
multilineBuilder.delete(0, multilineBuilder.length());
|
||||
} else {
|
||||
if (multilineBuilder.toString().trim().length() > 3) {
|
||||
multilineBuilder.append('\n');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (multiline.isNotMultiline() && pair[1].trim().startsWith(STRING_LITERAL_DELIMITER)) {
|
||||
multiline = Multiline.STRING_LITERAL;
|
||||
multilineBuilder.append(pair[1]);
|
||||
key = pair[0].trim();
|
||||
|
||||
if (pair[1].trim().indexOf(STRING_LITERAL_DELIMITER, 3) > -1) {
|
||||
multiline = Multiline.NONE;
|
||||
pair[1] = multilineBuilder.toString().trim();
|
||||
multilineBuilder.delete(0, multilineBuilder.length());
|
||||
} else {
|
||||
if (multilineBuilder.toString().trim().length() > 3) {
|
||||
multilineBuilder.append('\n');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (multiline == Multiline.ARRAY) {
|
||||
String lineWithoutComment = removeComment(line);
|
||||
multilineBuilder.append(lineWithoutComment);
|
||||
if (MULTILINE_ARRAY_REGEX_END.matcher(lineWithoutComment).matches()) {
|
||||
multiline = Multiline.NONE;
|
||||
value = multilineBuilder.toString();
|
||||
multilineBuilder.delete(0, multilineBuilder.length());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (multiline == Multiline.STRING) {
|
||||
multilineBuilder.append(line);
|
||||
if (line.contains("\"\"\"")) {
|
||||
multiline = Multiline.NONE;
|
||||
value = multilineBuilder.toString().trim();
|
||||
multilineBuilder.delete(0, multilineBuilder.length());
|
||||
} else {
|
||||
multilineBuilder.append('\n');
|
||||
continue;
|
||||
}
|
||||
} else if (multiline == Multiline.STRING_LITERAL) {
|
||||
multilineBuilder.append(line);
|
||||
if (line.contains(STRING_LITERAL_DELIMITER)) {
|
||||
multiline = Multiline.NONE;
|
||||
value = multilineBuilder.toString().trim();
|
||||
multilineBuilder.delete(0, multilineBuilder.length());
|
||||
} else {
|
||||
multilineBuilder.append('\n');
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
key = pair[0].trim();
|
||||
value = pair[1].trim();
|
||||
}
|
||||
|
||||
if (!isKeyValid(key)) {
|
||||
results.errors.append("Invalid key name: " + key + "\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
Object convertedValue = VALUE_ANALYSIS.convert(value);
|
||||
|
||||
if (convertedValue != INVALID) {
|
||||
results.addValue(key, convertedValue);
|
||||
} else {
|
||||
results.errors.append("Invalid key/value: " + key + " = " + value + "\n");
|
||||
}
|
||||
}
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
boolean inComment = false;
|
||||
AtomicInteger line = new AtomicInteger(1);
|
||||
Identifier identifier = null;
|
||||
Object value = null;
|
||||
|
||||
if (multiline != Multiline.NONE) {
|
||||
results.errors.append("Unterminated multiline " + multiline.toString().toLowerCase().replace('_', ' ') + "\n");
|
||||
for (int i = index.get(); i < tomlString.length(); i = index.incrementAndGet()) {
|
||||
char c = tomlString.charAt(i);
|
||||
|
||||
if (results.errors.hasErrors()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '#' && !inComment) {
|
||||
inComment = true;
|
||||
} else if (!Character.isWhitespace(c) && !inComment && identifier == null) {
|
||||
Identifier id = IDENTIFIER_CONVERTER.convert(tomlString, index, new Context(null, line, results.errors));
|
||||
|
||||
if (id != Identifier.INVALID) {
|
||||
if (id.isKey()) {
|
||||
identifier = id;
|
||||
} else if (id.isTable()) {
|
||||
results.startTables(id, line);
|
||||
} else if (id.isTableArray()) {
|
||||
results.startTableArray(id, line);
|
||||
}
|
||||
}
|
||||
} else if (c == '\n') {
|
||||
inComment = false;
|
||||
identifier = null;
|
||||
value = null;
|
||||
line.incrementAndGet();
|
||||
} else if (!inComment && identifier != null && identifier.isKey() && value == null && !Character.isWhitespace(c)) {
|
||||
value = ValueConverters.CONVERTERS.convert(tomlString, index, new Context(identifier, line, results.errors));
|
||||
|
||||
if (value instanceof Results.Errors) {
|
||||
results.errors.add((Results.Errors) value);
|
||||
} else {
|
||||
results.addValue(identifier.getName(), value, line);
|
||||
}
|
||||
} else if (value != null && !inComment && !Character.isWhitespace(c)) {
|
||||
results.errors.invalidTextAfterIdentifier(identifier, c, line.get());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private boolean isTableArray(String line) {
|
||||
return line.startsWith("[[");
|
||||
}
|
||||
|
||||
private String getTableArrayName(String line) {
|
||||
List<Object> resultValue = parse(parser().TableArray(), line);
|
||||
if (resultValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) resultValue.get(0);
|
||||
}
|
||||
|
||||
private boolean isTable(String line) {
|
||||
return line.startsWith("[");
|
||||
}
|
||||
|
||||
private String getTableName(String line) {
|
||||
List<Object> resultValue = parse(parser().Table(), line);
|
||||
if (resultValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) resultValue.get(0);
|
||||
}
|
||||
|
||||
private boolean isKeyValid(String key) {
|
||||
if (key.contains("#") || key.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isComment(String line) {
|
||||
if (line == null || line.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char[] chars = line.toCharArray();
|
||||
|
||||
for (char c : chars) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return c == '#';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String removeComment(String line) {
|
||||
line = line.trim();
|
||||
if (line.startsWith("\"")) {
|
||||
int startOfComment = line.indexOf('#', line.lastIndexOf('"'));
|
||||
if (startOfComment > -1) {
|
||||
return line.substring(0, startOfComment - 1).trim();
|
||||
}
|
||||
} else {
|
||||
int startOfComment = line.indexOf('#');
|
||||
if (startOfComment > -1) {
|
||||
return line.substring(0, startOfComment - 1).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private static enum Multiline {
|
||||
NONE, ARRAY, STRING, STRING_LITERAL;
|
||||
|
||||
public boolean isNotMultiline() {
|
||||
return this == NONE;
|
||||
}
|
||||
|
||||
public boolean isTrimmable() {
|
||||
return this == NONE || this == ARRAY;
|
||||
}
|
||||
}
|
||||
private TomlParser() {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
interface ValueConverter {
|
||||
|
||||
/**
|
||||
|
@ -8,7 +10,12 @@ interface ValueConverter {
|
|||
boolean canConvert(String s);
|
||||
|
||||
/**
|
||||
* @param s must already have been validated by {@link #canConvert(String)}
|
||||
* Partial validation. Stops after type terminator, rather than at EOI.
|
||||
*
|
||||
* @param s must already have been validated by {@link #canConvert(String)}
|
||||
* @param index where to start in s
|
||||
* @param line current line number, used for error reporting
|
||||
* @return a value or a {@link Results.Errors}
|
||||
*/
|
||||
Object convert(String s);
|
||||
Object convert(String s, AtomicInteger index, Context context);
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.parserunners.BasicParseRunner;
|
||||
|
||||
class ValueConverterUtils {
|
||||
static final Object INVALID = new Object();
|
||||
|
||||
static ValueParser parser() {
|
||||
return Parboiled.createParser(ValueParser.class);
|
||||
}
|
||||
|
||||
static <T> List<T> parse(Rule rule, String s) {
|
||||
return new BasicParseRunner<List<T>>(rule).run(s).resultValue;
|
||||
}
|
||||
|
||||
static boolean isComment(String line) {
|
||||
if (line == null || line.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char[] chars = line.toCharArray();
|
||||
|
||||
for (char c : chars) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return c == '#';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ValueConverterUtils() {}
|
||||
}
|
|
@ -3,28 +3,35 @@ package com.moandjiezana.toml;
|
|||
import static com.moandjiezana.toml.ArrayConverter.ARRAY_PARSER;
|
||||
import static com.moandjiezana.toml.BooleanConverter.BOOLEAN_PARSER;
|
||||
import static com.moandjiezana.toml.DateConverter.DATE_PARSER;
|
||||
import static com.moandjiezana.toml.ExponentConverter.EXPONENT_PARSER;
|
||||
import static com.moandjiezana.toml.FloatConverter.FLOAT_PARSER;
|
||||
import static com.moandjiezana.toml.IntegerConverter.INTEGER_PARSER;
|
||||
import static com.moandjiezana.toml.InlineTableConverter.INLINE_TABLE_PARSER;
|
||||
import static com.moandjiezana.toml.LiteralStringConverter.LITERAL_STRING_PARSER;
|
||||
import static com.moandjiezana.toml.MultilineLiteralStringConverter.MULTILINE_LITERAL_STRING_CONVERTER;
|
||||
import static com.moandjiezana.toml.MultilineStringConverter.MULTILINE_STRING_PARSER;
|
||||
import static com.moandjiezana.toml.NumberConverter.NUMBER_PARSER;
|
||||
import static com.moandjiezana.toml.StringConverter.STRING_PARSER;
|
||||
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class ValueConverters {
|
||||
|
||||
private static final ValueConverter[] PARSERS = {
|
||||
MULTILINE_STRING_PARSER, MULTILINE_LITERAL_STRING_CONVERTER, LITERAL_STRING_PARSER, STRING_PARSER, DATE_PARSER, EXPONENT_PARSER, INTEGER_PARSER, FLOAT_PARSER, BOOLEAN_PARSER, ARRAY_PARSER
|
||||
};
|
||||
|
||||
public Object convert(String value) {
|
||||
static final ValueConverters CONVERTERS = new ValueConverters();
|
||||
|
||||
Object convert(String value, AtomicInteger index, Context context) {
|
||||
String substring = value.substring(index.get());
|
||||
for (ValueConverter valueParser : PARSERS) {
|
||||
if (valueParser.canConvert(value)) {
|
||||
return valueParser.convert(value);
|
||||
if (valueParser.canConvert(substring)) {
|
||||
return valueParser.convert(value, index, context);
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID;
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), substring, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
private ValueConverters() {}
|
||||
|
||||
private static final ValueConverter[] PARSERS = {
|
||||
MULTILINE_STRING_PARSER, MULTILINE_LITERAL_STRING_CONVERTER, LITERAL_STRING_PARSER, STRING_PARSER, DATE_PARSER, NUMBER_PARSER, BOOLEAN_PARSER, ARRAY_PARSER, INLINE_TABLE_PARSER
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.support.ParseTreeUtils;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
@BuildParseTree
|
||||
class ValueParser extends BaseParser<List<Object>> {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ParsingResult<Object> parsingResult = new RecoveringParseRunner<Object>(ValueConverterUtils.parser().T()).run("'''abc''' # comment");
|
||||
|
||||
System.out.println(ParseTreeUtils.printNodeTree(parsingResult));
|
||||
}
|
||||
|
||||
public Rule T() {
|
||||
return Sequence("'''", OneOrMore(TestNot("'''"), ANY), "'''", Comment());
|
||||
}
|
||||
|
||||
public Rule Array() {
|
||||
return FirstOf(EmptyArray(), Sequence('[', startList(), OneOrMore(FirstOf(NonEmptyArray(), ' ', ',')), ']', endList()));
|
||||
}
|
||||
|
||||
public Rule Table() {
|
||||
return Sequence('[', startList(), Sequence(OneOrMore(NoneOf("[]")), pushToken(match())), ']', endList(), Comment());
|
||||
}
|
||||
|
||||
public Rule TableArray() {
|
||||
return Sequence('[', '[', startList(), Sequence(OneOrMore(NoneOf("[]")), pushToken(match())), ']', ']', endList(), FirstOf(EOI, Sequence(TestNot(']'), ANY)));
|
||||
}
|
||||
|
||||
public Rule LiteralString() {
|
||||
return FirstOf(EmptyLiteralString(), Sequence('\'', OneOrMore(TestNot("'"), ANY), startList(), pushToken(match()) , '\'', endList(), Comment()));
|
||||
}
|
||||
|
||||
public Rule MultilineLiteralString() {
|
||||
return FirstOf(EmptyMultilineLiteralString(), Sequence("'''", startList(), Sequence(OneOrMore(TestNot("'''"), ANY), pushToken(match())), "'''", endList(), Comment()));
|
||||
}
|
||||
|
||||
public Rule Boolean() {
|
||||
return Sequence(startList(), FirstOf("true", "false"), pushToken(match()), endList(), Comment());
|
||||
}
|
||||
|
||||
public Rule Integer() {
|
||||
return Sequence(startList(), Sequence(SignedNumber(), pushToken(match())), endList(), Comment());
|
||||
}
|
||||
|
||||
public Rule Exponent() {
|
||||
return Sequence(startList(), Sequence(Sequence(SignedNumber(), Optional(Sequence('.', Number())), FirstOf('e', 'E'), SignedNumber()), pushToken(match())), endList(), Comment());
|
||||
}
|
||||
|
||||
Rule NonEmptyArray() {
|
||||
return FirstOf(Array(), OneOrMore(TestNot(']'), FirstOf(StringToken(), Array(), ',', ' ', OtherValue())));
|
||||
}
|
||||
|
||||
Rule StringToken() {
|
||||
return Sequence(Sequence('"', ZeroOrMore(Sequence(TestNot('"'), ANY)), '"'), pushToken(match()));
|
||||
}
|
||||
|
||||
Rule EmptyLiteralString() {
|
||||
return Sequence('\'', '\'', startList(), pushToken(""), endList());
|
||||
}
|
||||
|
||||
Rule EmptyMultilineLiteralString() {
|
||||
return Sequence("'''", "'''", startList(), pushToken(""), endList(), Comment());
|
||||
}
|
||||
|
||||
Rule EmptyArray() {
|
||||
return Sequence('[', ']', startList(), endList());
|
||||
}
|
||||
|
||||
Rule SignedNumber() {
|
||||
return Sequence(Optional(AnyOf("+-")), Number());
|
||||
}
|
||||
|
||||
Rule Number() {
|
||||
return OneOrMore(CharRange('0', '9'));
|
||||
}
|
||||
|
||||
Rule OtherValue() {
|
||||
return Sequence(ZeroOrMore(NoneOf("],")), pushToken(match()));
|
||||
}
|
||||
|
||||
Rule Comment() {
|
||||
return FirstOf(EOI, OneOrMore(' ', Sequence('#', ZeroOrMore(ANY))));
|
||||
}
|
||||
|
||||
boolean startList() {
|
||||
ArrayList<Object> newTokens = new ArrayList<Object>();
|
||||
|
||||
if (!getContext().getValueStack().isEmpty()) {
|
||||
peek().add(newTokens);
|
||||
}
|
||||
push(newTokens);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean endList() {
|
||||
if (getContext().getValueStack().size() > 1) {
|
||||
pop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean pushToken(String s) {
|
||||
peek().add(s);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
@ -17,14 +21,14 @@ public class ArrayTest {
|
|||
public void should_get_array() throws Exception {
|
||||
Toml toml = new Toml().parse("list = [\"a\", \"b\", \"c\"]");
|
||||
|
||||
assertEquals(asList("a", "b", "c"), toml.getList("list", String.class));
|
||||
assertEquals(asList("a", "b", "c"), toml.<String>getList("list"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_allow_multiline_array() throws Exception {
|
||||
Toml toml = new Toml().parse(file("should_allow_multiline_array"));
|
||||
|
||||
assertEquals(asList("a", "b", "c"), toml.getList("a", String.class));
|
||||
assertEquals(asList("a", "b", "c"), toml.<String>getList("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -32,7 +36,17 @@ public class ArrayTest {
|
|||
public void should_get_nested_arrays() throws Exception {
|
||||
Toml clients = new Toml().parse("data = [ [\"gamma\", \"delta\"], [1, 2]] # just an update to make sure parsers support it");
|
||||
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.getList("data", String.class));
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.<String>getList("data"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_deeply_nested_arrays() throws Exception {
|
||||
List<List<?>> data = new Toml().parse("data = [[[1], [2]], [3, 4]]").getList("data");
|
||||
|
||||
assertThat(data, hasSize(2));
|
||||
assertEquals(Arrays.asList(1L), data.get(0).get(0));
|
||||
assertEquals(asList(2L), data.get(0).get(1));
|
||||
assertEquals(asList(3L, 4L), data.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -40,7 +54,7 @@ public class ArrayTest {
|
|||
public void should_get_nested_arrays_with_no_space_between_outer_and_inner_array() throws Exception {
|
||||
Toml clients = new Toml().parse("data = [[\"gamma\", \"delta\"], [1, 2]] # just an update to make sure parsers support it");
|
||||
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.getList("data", String.class));
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.<String>getList("data"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -54,7 +68,40 @@ public class ArrayTest {
|
|||
public void should_ignore_comma_at_end_of_array() throws Exception {
|
||||
Toml toml = new Toml().parse("key=[1,2,3,]");
|
||||
|
||||
assertEquals(asList(1L, 2L, 3L), toml.getList("key", Long.class));
|
||||
assertEquals(asList(1L, 2L, 3L), toml.<Long>getList("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_mixed_string_types() throws Exception {
|
||||
Toml toml = new Toml().parse("key = [\"a\", 'b', \"\"\"c\"\"\", '''d''']");
|
||||
|
||||
assertThat(toml.<String>getList("key"), contains("a", "b", "c", "d"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_array_terminator_in_strings() throws Exception {
|
||||
Toml toml = new Toml().parse("key = [\"a]\", 'b]', \"\"\"c]\"\"\", '''d]''']");
|
||||
|
||||
assertThat(toml.<String>getList("key"), contains("a]", "b]", "c]", "d]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_array_of_inline_tables() throws Exception {
|
||||
Toml toml = new Toml().parse(getClass().getResourceAsStream("should_support_array_of_inline_tables.toml"));
|
||||
|
||||
assertThat(toml.getList("points"), hasSize(4));
|
||||
assertEquals(1, toml.getLong("points[0].x").longValue());
|
||||
assertEquals(2, toml.getLong("points[0].y").longValue());
|
||||
assertEquals(3, toml.getLong("points[0].z").longValue());
|
||||
assertEquals(7, toml.getLong("points[1].x").longValue());
|
||||
assertEquals(8, toml.getLong("points[1].y").longValue());
|
||||
assertEquals(9, toml.getLong("points[1].z").longValue());
|
||||
assertEquals(2, toml.getLong("points[2].x").longValue());
|
||||
assertEquals(4, toml.getLong("points[2].y").longValue());
|
||||
assertEquals(8, toml.getLong("points[2].z").longValue());
|
||||
assertEquals("3", toml.getString("points[3].x"));
|
||||
assertEquals("6", toml.getString("points[3].y"));
|
||||
assertEquals("12", toml.getString("points[3].z"));
|
||||
}
|
||||
|
||||
private File file(String file) {
|
||||
|
|
123
src/test/java/com/moandjiezana/toml/BareKeysTest.java
Normal file
123
src/test/java/com/moandjiezana/toml/BareKeysTest.java
Normal file
|
@ -0,0 +1,123 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BareKeysTest {
|
||||
|
||||
@Test
|
||||
public void should_ignore_spaces_around_key_segments() throws Exception {
|
||||
Toml toml = new Toml().parse("[ a . b . c ] \n key = \"a\"");
|
||||
|
||||
assertEquals("a", toml.getString("a.b.c.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a_a = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a_a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group_a]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group_a.a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a1 = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a1").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group1]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group1.a").intValue());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_characters_outside_accept_range_are_used_in_table_name() throws Exception {
|
||||
new Toml().parse("[~]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_characters_outside_accept_range_are_used_in_table_array_name() throws Exception {
|
||||
new Toml().parse("[[~]]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_dots_in_key_name() throws Exception {
|
||||
new Toml().parse("a.b = 1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_characters_outside_accept_range_are_used_in_key_name() throws Exception {
|
||||
new Toml().parse("~ = 1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_sharp_sign_in_table_name() throws Exception {
|
||||
new Toml().parse("[group#]\nkey=1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_spaces_in_table_name() throws Exception {
|
||||
new Toml().parse("[valid key]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_sharp_sign_in_table_array_name() throws Exception {
|
||||
new Toml().parse("[[group#]]\nkey=1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_spaces_in_table_array_name() throws Exception {
|
||||
new Toml().parse("[[valid key]]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_question_marks_in_key_name() throws Exception {
|
||||
new Toml().parse("key?=true");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_empty_table_name() {
|
||||
new Toml().parse("[]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_name_ending_with_empty_table_name() {
|
||||
new Toml().parse("[a.]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_name_containing_empty_table_name() {
|
||||
new Toml().parse("[a..b]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_name_starting_with_empty_table_name() {
|
||||
new Toml().parse("[.b]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_array_name_ending_with_empty_table_name() {
|
||||
new Toml().parse("[[a.]]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_array_name_containing_empty_table_name() {
|
||||
new Toml().parse("[[a..b]]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_nested_table_array_name_starting_with_empty_table_name() {
|
||||
new Toml().parse("[[.b]]");
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import java.util.TimeZone;
|
|||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
@ -108,21 +109,36 @@ public class BurntSushiValidTest {
|
|||
run("key-equals-nospace");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Ignore
|
||||
public void key_space() throws Exception {
|
||||
run("key-space");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void key_space_modified() throws Exception {
|
||||
run("key-space-modified");
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
public void key_special_chars() throws Exception {
|
||||
run("key-special-chars");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void key_special_chars_modified() throws Exception {
|
||||
run("key-special-chars-modified");
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
public void keys_with_dots() throws Exception {
|
||||
run("keys-with-dots");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keys_with_dots_modified() throws Exception {
|
||||
run("keys-with-dots-modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void long_float() throws Exception {
|
||||
run("long-float");
|
||||
|
@ -133,11 +149,16 @@ public class BurntSushiValidTest {
|
|||
run("long-integer");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Ignore
|
||||
public void multiline_string() throws Exception {
|
||||
run("multiline-string");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiline_string_modified() throws Exception {
|
||||
run("multiline-string-modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void raw_multiline_string() throws Exception {
|
||||
run("raw-multiline-string");
|
||||
|
@ -153,9 +174,14 @@ public class BurntSushiValidTest {
|
|||
run("string-empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Ignore
|
||||
public void string_escapes() throws Exception {
|
||||
run("string-escapes");
|
||||
run("string-escapes-modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void string_escapes_modified() throws Exception {
|
||||
run("string-escapes-modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -198,12 +224,12 @@ public class BurntSushiValidTest {
|
|||
run("table-sub-empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Ignore
|
||||
public void table_whitespace() throws Exception {
|
||||
run("table-whitespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Ignore
|
||||
public void table_with_pound() throws Exception {
|
||||
run("table-with-pound");
|
||||
}
|
||||
|
|
123
src/test/java/com/moandjiezana/toml/ErrorMessagesTest.java
Normal file
123
src/test/java/com/moandjiezana/toml/ErrorMessagesTest.java
Normal file
|
@ -0,0 +1,123 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class ErrorMessagesTest {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException e = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void should_message_invalid_table() throws Exception {
|
||||
e.expectMessage("Invalid table definition on line 1: [in valid]");
|
||||
|
||||
new Toml().parse("[in valid]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_duplicate_table() throws Exception {
|
||||
e.expectMessage("Duplicate table definition on line 2: [again]");
|
||||
|
||||
new Toml().parse("[again]\n[again]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_empty_implicit_table_name() throws Exception {
|
||||
e.expectMessage("Invalid table definition due to empty implicit table name: [a..b]");
|
||||
|
||||
new Toml().parse("[a..b]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_duplicate_key() throws Exception {
|
||||
e.expectMessage("Duplicate key on line 2: k");
|
||||
|
||||
new Toml().parse("k = 1\n k = 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_invalid_key() throws Exception {
|
||||
e.expectMessage("Key is not followed by an equals sign on line 1: k\" = 1");
|
||||
|
||||
new Toml().parse("k\" = 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_invalid_table_array() throws Exception {
|
||||
e.expectMessage("Invalid table array definition on line 1: [[in valid]]");
|
||||
|
||||
new Toml().parse("[[in valid]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_invalid_value() throws Exception {
|
||||
e.expectMessage("Invalid text after key k on line 1");
|
||||
|
||||
new Toml().parse("k = 1 t");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_unterminated_multiline_literal_string() throws Exception {
|
||||
e.expectMessage("Unterminated value on line 1: k = '''abc");
|
||||
|
||||
new Toml().parse("k = '''abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_unterminated_multiline_string() throws Exception {
|
||||
e.expectMessage("Unterminated value on line 1: k = \"\"\"abc\"\"");
|
||||
|
||||
new Toml().parse("k = \"\"\"abc\"\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_unterminated_array() throws Exception {
|
||||
e.expectMessage("Unterminated value on line 1: k = [\"abc\"");
|
||||
|
||||
new Toml().parse("k = [\"abc\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_unterminated_inline_table() throws Exception {
|
||||
e.expectMessage("Unterminated value on line 1: k = { a = \"abc\"");
|
||||
|
||||
new Toml().parse("k = { a = \"abc\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_key_without_equals() throws Exception {
|
||||
e.expectMessage("Key is not followed by an equals sign on line 2: k");
|
||||
|
||||
new Toml().parse("\nk\n=3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_display_correct_line_number_with_literal_multiline_string() throws Exception {
|
||||
e.expectMessage("on line 8");
|
||||
|
||||
new Toml().parse("[table]\n\n k = '''abc\n\ndef\n'''\n # comment \n j = 4.\n l = 5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_display_correct_line_number_with_multiline_string() throws Exception {
|
||||
e.expectMessage("on line 9");
|
||||
|
||||
new Toml().parse("[table]\n\n k = \"\"\"\nabc\n\ndef\n\"\"\"\n # comment \n j = 4.\n l = 5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_display_correct_line_number_with_array() throws Exception {
|
||||
e.expectMessage("on line 10");
|
||||
|
||||
new Toml().parse("[table]\n\n k = [\"\"\"\nabc\n\ndef\n\"\"\"\n, \n # comment \n j = 4.,\n l = 5\n]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_message_heterogeneous_array() throws Exception {
|
||||
e.expectMessage("k becomes a heterogeneous array on line 2");
|
||||
|
||||
new Toml().parse("k = [ 1,\n 1.1 ]");
|
||||
}
|
||||
}
|
237
src/test/java/com/moandjiezana/toml/InlineTableTest.java
Normal file
237
src/test/java/com/moandjiezana/toml/InlineTableTest.java
Normal file
|
@ -0,0 +1,237 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class InlineTableTest {
|
||||
|
||||
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
|
||||
|
||||
@Rule
|
||||
public ExpectedException e = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void should_read_empty_inline_table() throws Exception {
|
||||
Toml toml = new Toml().parse("key = {}");
|
||||
|
||||
assertNotNull(toml.getTable("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_with_strings() throws Exception {
|
||||
Toml toml = new Toml().parse("name = { first = \"Tom\", last = \"Preston-Werner\"}");
|
||||
|
||||
assertEquals("Tom", toml.getTable("name").getString("first"));
|
||||
assertEquals("Preston-Werner", toml.getString("name.last"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_with_integers() throws Exception {
|
||||
Toml toml = new Toml().parse("point = { x = 1, y = 2 }");
|
||||
|
||||
assertEquals(1, toml.getTable("point").getLong("x").longValue());
|
||||
assertEquals(2, toml.getLong("point.y").longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_with_floats() throws Exception {
|
||||
Toml toml = new Toml().parse("point = { x = 1.5, y = 2.3 }");
|
||||
|
||||
assertEquals(1.5, toml.getTable("point").getDouble("x").doubleValue(), 0);
|
||||
assertEquals(2.3, toml.getDouble("point.y").doubleValue(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_with_booleans() throws Exception {
|
||||
Toml toml = new Toml().parse("point = { x = false, y = true }");
|
||||
|
||||
assertTrue(toml.getTable("point").getBoolean("y"));
|
||||
assertFalse(toml.getBoolean("point.x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_with_dates() throws Exception {
|
||||
Toml toml = new Toml().parse("point = { x = 2015-02-09T22:05:00Z, y = 2015-02-09T21:05:00Z }");
|
||||
|
||||
Calendar x = Calendar.getInstance(UTC);
|
||||
x.set(2015, Calendar.FEBRUARY, 9, 22, 5, 00);
|
||||
x.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
Calendar y = Calendar.getInstance(UTC);
|
||||
y.set(2015, Calendar.FEBRUARY, 9, 21, 5, 00);
|
||||
y.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
assertEquals(x.getTime(), toml.getTable("point").getDate("x"));
|
||||
assertEquals(y.getTime(), toml.getDate("point.y"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_arrays() throws Exception {
|
||||
Toml toml = new Toml().parse("arrays = { integers = [1, 2, 3], strings = [\"a\", \"b\", \"c\"] }");
|
||||
|
||||
assertThat(toml.<Long>getList("arrays.integers"), contains(1L, 2L, 3L));
|
||||
assertThat(toml.<String>getList("arrays.strings"), contains("a", "b", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_nested_arrays() throws Exception {
|
||||
Toml toml = new Toml().parse("arrays = { nested = [[1, 2, 3], [4, 5, 6]] }").getTable("arrays");
|
||||
|
||||
List<List<Long>> nested = toml.<List<Long>>getList("nested");
|
||||
assertThat(nested, hasSize(2));
|
||||
assertThat(nested.get(0), contains(1L, 2L, 3L));
|
||||
assertThat(nested.get(1), contains(4L, 5L, 6L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_mixed_inline_table() throws Exception {
|
||||
Toml toml = new Toml().parse("point = { date = 2015-02-09T22:05:00Z, bool = true, integer = 123, float = 123.456, string = \"abc\", list = [5, 6, 7, 8] }").getTable("point");
|
||||
|
||||
|
||||
Calendar date = Calendar.getInstance(UTC);
|
||||
date.set(2015, Calendar.FEBRUARY, 9, 22, 5, 00);
|
||||
date.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
assertEquals(date.getTime(), toml.getDate("date"));
|
||||
assertTrue(toml.getBoolean("bool"));
|
||||
assertEquals(123, toml.getLong("integer").intValue());
|
||||
assertEquals(123.456, toml.getDouble("float"), 0);
|
||||
assertEquals("abc", toml.getString("string"));
|
||||
assertThat(toml.<Long>getList("list"), contains(5L, 6L, 7L, 8L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_nested_inline_tables() throws Exception {
|
||||
Toml tables = new Toml().parse("tables = { t1 = { t1_1 = 1, t1_2 = 2}, t2 = { t2_1 = { t2_1_1 = \"a\" }} }").getTable("tables");
|
||||
|
||||
assertEquals(1L, tables.getLong("t1.t1_1").longValue());
|
||||
assertEquals(2L, tables.getLong("t1.t1_2").longValue());
|
||||
assertEquals("a", tables.getString("t2.t2_1.t2_1_1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_all_string_types() throws Exception {
|
||||
Toml strings = new Toml().parse("strings = { literal = 'ab]\"c', multiline = \"\"\"de]\"f\"\"\", multiline_literal = '''gh]\"i''' }").getTable("strings");
|
||||
|
||||
assertEquals("ab]\"c", strings.getString("literal"));
|
||||
assertEquals("de]\"f", strings.getString("multiline"));
|
||||
assertEquals("gh]\"i", strings.getString("multiline_literal"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_inline_table_in_regular_table() throws Exception {
|
||||
Toml toml = new Toml().parse("[tbl]\n tbl = { tbl = 1 }");
|
||||
|
||||
assertEquals(1, toml.getLong("tbl.tbl.tbl").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_mix_with_tables() throws Exception {
|
||||
Toml toml = new Toml().parse("t = { k = 1 }\n [b]\n k = 2\n t = { k = 3}");
|
||||
|
||||
assertEquals(1, toml.getLong("t.k").intValue());
|
||||
assertEquals(2, toml.getLong("b.k").intValue());
|
||||
assertEquals(3, toml.getLong("b.t.k").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_add_properties_to_existing_inline_table() throws Exception {
|
||||
Toml toml = new Toml().parse("[a]\n b = {k = 1}\n [a.b.c]\n k = 2");
|
||||
|
||||
assertEquals(1, toml.getLong("a.b.k").intValue());
|
||||
assertEquals(2, toml.getLong("a.b.c.k").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_mix_with_table_arrays() throws Exception {
|
||||
Toml toml = new Toml().parse("t = { k = 1 }\n [[b]]\n t = { k = 2 }\n [[b]]\n t = { k = 3 }");
|
||||
|
||||
assertEquals(1, toml.getLong("t.k").intValue());
|
||||
assertEquals(2, toml.getLong("b[0].t.k").intValue());
|
||||
assertEquals(3, toml.getLong("b[1].t.k").intValue());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_invalid_key() throws Exception {
|
||||
new Toml().parse("tbl = { a. = 1 }");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_unterminated() throws Exception {
|
||||
new Toml().parse("tbl = { a = 1 ");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_invalid_value() throws Exception {
|
||||
new Toml().parse("tbl = { a = abc }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_key_duplicated_inside_inline_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate key on line 1: a");
|
||||
|
||||
new Toml().parse("tbl = { a = 1, a = 2 }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_duplicated_by_other_key() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate key on line 2: tbl");
|
||||
|
||||
new Toml().parse("tbl = { a = 1 }\n tbl = 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_duplicated_by_other_inline_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate table definition on line 2: [tbl]");
|
||||
|
||||
new Toml().parse("tbl = { a = 1 }\n tbl = {}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_duplicated_by_top_level_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate table definition on line 2: [tbl]");
|
||||
|
||||
new Toml().parse("tbl = {}\n [tbl]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_duplicates_second_level_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate table definition on line 3: [a.b]");
|
||||
|
||||
new Toml().parse("[a.b]\n [a]\n b = {}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_inline_table_duplicates_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate table definition on line 3: [a.b]");
|
||||
|
||||
new Toml().parse("[a.b]\n [a]\n b = {}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fail_when_second_level_table_duplicates_inline_table() throws Exception {
|
||||
e.expect(IllegalStateException.class);
|
||||
e.expectMessage("Duplicate table definition on line 3: [a.b]");
|
||||
|
||||
new Toml().parse("[a]\n b = {}\n [a.b]");
|
||||
}
|
||||
}
|
|
@ -51,14 +51,14 @@ public class NumberTest {
|
|||
public void should_get_exponent() throws Exception {
|
||||
Toml toml = new Toml().parse("lower_case = 1e6\nupper_case = 2E6\nwith_plus = 5e+22\nboth_plus = +5E+22\nnegative = -2E-2\nfractional = 6.626e-34");
|
||||
|
||||
assertEquals(Math.pow(1, 6), toml.getDouble("lower_case"), 0.0);
|
||||
assertEquals(Math.pow(2, 6), toml.getDouble("upper_case"), 0.0);
|
||||
assertEquals(Math.pow(5, 22), toml.getDouble("with_plus"), 0.0);
|
||||
assertEquals(Math.pow(5, 22), toml.getDouble("both_plus"), 0.0);
|
||||
assertEquals(Math.pow(-2, -2), toml.getDouble("negative"), 0.0);
|
||||
assertEquals(Math.pow(6.626D, -34), toml.getDouble("fractional"), 0.0);
|
||||
assertEquals(1e6, toml.getDouble("lower_case"), 0.0);
|
||||
assertEquals(2E6, toml.getDouble("upper_case"), 0.0);
|
||||
assertEquals(5e22, toml.getDouble("with_plus"), 0.0);
|
||||
assertEquals(5e22, toml.getDouble("both_plus"), 0.0);
|
||||
assertEquals(-2e-2, toml.getDouble("negative"), 0.0);
|
||||
assertEquals(6.626D * Math.pow(10, -34), toml.getDouble("fractional"), 0.0);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_invalid_number() throws Exception {
|
||||
new Toml().parse("a = 200-");
|
||||
|
@ -84,6 +84,12 @@ public class NumberTest {
|
|||
new Toml().parse("answer = -.12345");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_float_with_sign_after_dot() {
|
||||
new Toml().parse("answer = 1.-1");
|
||||
new Toml().parse("answer = 1.+1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_float_without_digits_after_dot() {
|
||||
new Toml().parse("answer = 1.");
|
||||
|
@ -93,4 +99,34 @@ public class NumberTest {
|
|||
public void should_fail_on_negative_float_without_digits_after_dot() {
|
||||
new Toml().parse("answer = -1.");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_exponent_without_digits_after_dot() {
|
||||
new Toml().parse("answer = 1.E1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_negative_exponent_without_digits_after_dot() {
|
||||
new Toml().parse("answer = -1.E1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_exponent_with_dot_in_exponent_part() {
|
||||
new Toml().parse("answer = -1E1.0");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_exponent_without_numbers_after_E() {
|
||||
new Toml().parse("answer = -1E");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_exponent_with_two_E() {
|
||||
new Toml().parse("answer = -1E1E1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_float_with_two_dots() {
|
||||
new Toml().parse("answer = 1.1.1");
|
||||
}
|
||||
}
|
||||
|
|
137
src/test/java/com/moandjiezana/toml/QuotedKeysTest.java
Normal file
137
src/test/java/com/moandjiezana/toml/QuotedKeysTest.java
Normal file
|
@ -0,0 +1,137 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class QuotedKeysTest {
|
||||
|
||||
@Test
|
||||
public void should_accept_quoted_key_for_value() throws Exception {
|
||||
Toml toml = new Toml().parse("\"127.0.0.1\" = \"localhost\" \n \"character encoding\" = \"UTF-8\" \n \"ʎǝʞ\" = \"value\"");
|
||||
|
||||
assertEquals("localhost", toml.getString("\"127.0.0.1\""));
|
||||
assertEquals("UTF-8", toml.getString("\"character encoding\""));
|
||||
assertEquals("value", toml.getString("\"ʎǝʞ\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_accept_quoted_key_for_table_name() throws Exception {
|
||||
Toml toml = new Toml().parse("[\"abc def\"]\n val = 1");
|
||||
|
||||
assertEquals(1L, toml.getTable("\"abc def\"").getLong("val").longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_accept_partially_quoted_table_name() throws Exception {
|
||||
Toml toml = new Toml().parse("[dog.\"tater.man\"] \n type = \"pug0\" \n[dog.tater] \n type = \"pug1\"\n[dog.tater.man] \n type = \"pug2\"");
|
||||
Toml dogs = toml.getTable("dog");
|
||||
|
||||
assertEquals("pug0", dogs.getTable("\"tater.man\"").getString("type"));
|
||||
assertEquals("pug1", dogs.getTable("tater").getString("type"));
|
||||
assertEquals("pug2", dogs.getTable("tater").getTable("man").getString("type"));
|
||||
assertEquals("pug0", toml.getString("dog.\"tater.man\".type"));
|
||||
assertEquals("pug2", toml.getString("dog.tater.man.type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void should_conserve_quoted_key_in_map() throws Exception {
|
||||
Toml toml = new Toml().parse("[dog.\"tater.man\"] \n type = \"pug0\" \n[dog.tater] \n type = \"pug1\"\n[dog.tater.man] \n type = \"pug2\"");
|
||||
Toml dogs = toml.getTable("dog");
|
||||
|
||||
Map<String, Map<String, Object>> map = dogs.to(Map.class);
|
||||
|
||||
assertEquals("pug0", map.get("\"tater.man\"").get("type"));
|
||||
assertEquals("pug1", map.get("tater").get("type"));
|
||||
assertEquals("pug2", ((Map<String, Object>) map.get("tater").get("man")).get("type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_convert_quoted_keys_to_map_but_not_to_object_fields() throws Exception {
|
||||
Quoted quoted = new Toml().parse("\"ʎǝʞ\" = \"value\" \n[map] \n \"ʎǝʞ\" = \"value\"").to(Quoted.class);
|
||||
|
||||
assertNull(quoted.ʎǝʞ);
|
||||
assertEquals("value", quoted.map.get("\"ʎǝʞ\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_table_array_index_with_quoted_key() throws Exception {
|
||||
Toml toml = new Toml().parse("[[ dog. \" type\" ]] \n name = \"type0\" \n [[dog.\" type\"]] \n name = \"type1\"");
|
||||
|
||||
assertEquals("type0", toml.getString("dog.\" type\"[0].name"));
|
||||
assertEquals("type1", toml.getString("dog.\" type\"[1].name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_table_array_index_with_dot_in_quoted_key() throws Exception {
|
||||
Toml toml = new Toml().parse("[[ dog. \"a.type\" ]] \n name = \"type0\"");
|
||||
|
||||
assertEquals("type0", toml.getString("dog.\"a.type\"[0].name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_quoted_key_containing_square_brackets() throws Exception {
|
||||
Toml toml = new Toml().parse("[dog.\" type[abc]\"] \n name = \"type0\" \n [dog.\" type[1]\"] \n \"name[]\" = \"type1\"");
|
||||
|
||||
assertEquals("type0", toml.getString("dog.\" type[abc]\".name"));
|
||||
assertEquals("type1", toml.getString("dog.\" type[1]\".\"name[]\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_quoted_key_containing_escaped_quote() throws Exception {
|
||||
Toml toml = new Toml().parse("[dog.\"ty\\\"pe\"] \n \"na\\\"me\" = \"type0\"");
|
||||
|
||||
assertEquals("type0", toml.getString("dog.\"ty\\\"pe\".\"na\\\"me\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_fully_quoted_table_name() throws Exception {
|
||||
Toml toml = new Toml().parse("[\"abc.def\"] \n key = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("\"abc.def\".key").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_whitespace_around_key_segments() throws Exception {
|
||||
Toml toml = new Toml().parse("[ dog. \"type\". breed ] \n name = \"type0\"");
|
||||
|
||||
assertEquals("type0", toml.getString("dog.\"type\".breed.name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_unicode() throws Exception {
|
||||
Toml toml = new Toml().parse("[[\"\\u00B1\"]]\n \"\\u00B1\" = \"a\"\n [\"\\u00B11\"]\n \"±\" = 1");
|
||||
|
||||
assertThat(toml.getTables("\"±\""), hasSize(1));
|
||||
assertEquals("a", toml.getTables("\"±\"").get(0).getString("\"±\""));
|
||||
assertEquals(1, toml.getTable("\"±1\"").getLong("\"±\"").intValue());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_malformed_quoted_key() throws Exception {
|
||||
new Toml().parse("k\"ey\" = 1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_malformed_quoted_table() throws Exception {
|
||||
new Toml().parse("[a\"bc\"]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_malformed_quoted_nested_table() throws Exception {
|
||||
new Toml().parse("[a.a\"bc\"]");
|
||||
}
|
||||
|
||||
private static class Quoted {
|
||||
|
||||
String ʎǝʞ;
|
||||
|
||||
Map<String, Object> map;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,17 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Test;
|
||||
|
@ -38,7 +42,7 @@ public class RealWorldTest {
|
|||
assertEquals("192.168.1.1", database.getString("server"));
|
||||
assertEquals(5000L, database.getLong("connection_max").longValue());
|
||||
assertTrue(database.getBoolean("enabled"));
|
||||
assertEquals(Arrays.asList(8001L, 8001L, 8002L), database.getList("ports", Long.class));
|
||||
assertEquals(Arrays.asList(8001L, 8001L, 8002L), database.<Long>getList("ports"));
|
||||
|
||||
Toml servers = toml.getTable("servers");
|
||||
Toml alphaServers = servers.getTable("alpha");
|
||||
|
@ -50,8 +54,8 @@ public class RealWorldTest {
|
|||
assertEquals("中国", betaServers.getString("country"));
|
||||
|
||||
Toml clients = toml.getTable("clients");
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.getList("data", String.class));
|
||||
assertEquals(asList("alpha", "omega"), clients.getList("hosts", String.class));
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.<String>getList("data"));
|
||||
assertEquals(asList("alpha", "omega"), clients.<String>getList("hosts"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -59,13 +63,77 @@ public class RealWorldTest {
|
|||
Toml toml = new Toml().parse(new File(getClass().getResource("hard_example.toml").getFile()));
|
||||
|
||||
assertEquals("You'll hate me after this - #", toml.getString("the.test_string"));
|
||||
assertEquals(asList("] ", " # "), toml.getList("the.hard.test_array", String.class));
|
||||
assertEquals(asList("Test #11 ]proved that", "Experiment #9 was a success"), toml.getList("the.hard.test_array2", String.class));
|
||||
assertEquals(asList("] ", " # "), toml.<String>getList("the.hard.test_array"));
|
||||
assertEquals(asList("Test #11 ]proved that", "Experiment #9 was a success"), toml.<String>getList("the.hard.test_array2"));
|
||||
assertEquals(" Same thing, but with a string #", toml.getString("the.hard.another_test_string"));
|
||||
assertEquals(" And when \"'s are in the string, along with # \"", toml.getString("the.hard.harder_test_string"));
|
||||
Toml theHardBit = toml.getTable("the.hard.bit#");
|
||||
assertEquals("You don't think some user won't do that?", theHardBit.getString("what?"));
|
||||
assertEquals(asList("]"), theHardBit.getList("multi_line_array", String.class));
|
||||
Toml theHardBit = toml.getTable("the.hard.\"bit#\"");
|
||||
assertEquals("You don't think some user won't do that?", theHardBit.getString("\"what?\""));
|
||||
assertEquals(asList("]"), theHardBit.<String>getList("multi_line_array"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void should_parse_current_version_example() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("example-v0.3.0.toml").getFile()));
|
||||
|
||||
assertEquals("value", toml.getString("Table.key"));
|
||||
assertEquals("pug", toml.getString("dog.tater.type"));
|
||||
assertNotNull(toml.getTable("x.y.z").getTable("w"));
|
||||
assertEquals("I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF.", toml.getString("String.basic"));
|
||||
assertEquals("One\nTwo", toml.getString("String.Multiline.key3"));
|
||||
assertEquals(toml.getString("String.Multiline.key3"), toml.getString("String.Multiline.key1"));
|
||||
assertEquals(toml.getString("String.Multiline.key3"), toml.getString("String.Multiline.key2"));
|
||||
assertEquals("The quick brown fox jumps over the lazy dog.", toml.getString("String.Multilined.Singleline.key3"));
|
||||
assertEquals(toml.getString("String.Multilined.Singleline.key3"), toml.getString("String.Multilined.Singleline.key1"));
|
||||
assertEquals(toml.getString("String.Multilined.Singleline.key3"), toml.getString("String.Multilined.Singleline.key2"));
|
||||
assertEquals("C:\\Users\\nodejs\\templates", toml.getString("String.Literal.winpath"));
|
||||
assertEquals("\\\\ServerX\\admin$\\system32\\", toml.getString("String.Literal.winpath2"));
|
||||
assertEquals("Tom \"Dubs\" Preston-Werner", toml.getString("String.Literal.quoted"));
|
||||
assertEquals("<\\i\\c*\\s*>", toml.getString("String.Literal.regex"));
|
||||
assertEquals("I [dw]on't need \\d{2} apples", toml.getString("String.Literal.Multiline.regex2"));
|
||||
assertEquals("The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", toml.getString("String.Literal.Multiline.lines"));
|
||||
assertEquals(99, toml.getLong("Integer.key1").intValue());
|
||||
assertEquals(42, toml.getLong("Integer.key2").intValue());
|
||||
assertEquals(0, toml.getLong("Integer.key3").intValue());
|
||||
assertEquals(-17, toml.getLong("Integer.key4").intValue());
|
||||
assertEquals(1.0, toml.getDouble("Float.fractional.key1").doubleValue(), 0);
|
||||
assertEquals(3.1415, toml.getDouble("Float.fractional.key2").doubleValue(), 0);
|
||||
assertEquals(-0.01, toml.getDouble("Float.fractional.key3").doubleValue(), 0);
|
||||
assertEquals(5e+22, toml.getDouble("Float.exponent.key1").doubleValue(), 0);
|
||||
assertEquals(1e6, toml.getDouble("Float.exponent.key2").longValue(), 0);
|
||||
assertEquals(-2E-2, toml.getDouble("Float.exponent.key3").doubleValue(), 0);
|
||||
assertEquals(6.626e-34, toml.getDouble("Float.both.key").doubleValue(), 0);
|
||||
assertTrue(toml.getBoolean("Booleans.True"));
|
||||
assertFalse(toml.getBoolean("Booleans.False"));
|
||||
assertThat(toml.<Long>getList("Array.key1"), contains(1L, 2L, 3L));
|
||||
assertThat(toml.<String>getList("Array.key2"), contains("red", "yellow", "green"));
|
||||
assertEquals(asList(asList(1L, 2L), asList(3L, 4L, 5L)), toml.<List<Long>>getList("Array.key3"));
|
||||
assertEquals(asList(asList(1L, 2L), asList("a", "b", "c")), toml.<List<Long>>getList("Array.key4"));
|
||||
assertThat(toml.<Long>getList("Array.key5"), contains(1L, 2L, 3L));
|
||||
assertThat(toml.<Long>getList("Array.key6"), contains(1L, 2L));
|
||||
assertEquals("Hammer", toml.getString("products[0].name"));
|
||||
assertEquals(738594937, toml.getLong("products[0].sku").intValue());
|
||||
assertNotNull(toml.getTable("products[1]"));
|
||||
assertEquals("Nail", toml.getString("products[2].name"));
|
||||
assertEquals(284758393, toml.getLong("products[2].sku").intValue());
|
||||
assertEquals("gray", toml.getString("products[2].color"));
|
||||
assertEquals("apple", toml.getString("fruit[0].name"));
|
||||
assertEquals("red", toml.getString("fruit[0].physical.color"));
|
||||
assertEquals("round", toml.getString("fruit[0].physical.shape"));
|
||||
assertEquals("red delicious", toml.getString("fruit[0].variety[0].name"));
|
||||
assertEquals("granny smith", toml.getString("fruit[0].variety[1].name"));
|
||||
assertEquals("banana", toml.getString("fruit[1].name"));
|
||||
assertEquals("plantain", toml.getString("fruit[1].variety[0].name"));
|
||||
|
||||
Calendar dob = Calendar.getInstance();
|
||||
dob.set(1979, Calendar.MAY, 27, 7, 32, 0);
|
||||
dob.set(Calendar.MILLISECOND, 0);
|
||||
dob.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
assertEquals(dob.getTime(), toml.getDate("Datetime.key1"));
|
||||
assertEquals(dob.getTime(), toml.getDate("Datetime.key2"));
|
||||
dob.set(Calendar.MILLISECOND, 999);
|
||||
assertEquals(dob.getTime(), toml.getDate("Datetime.key3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -69,7 +69,7 @@ public class StringTest {
|
|||
public void should_support_special_characters_in_strings() {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_support_special_characters_in_strings.toml").getFile()));
|
||||
|
||||
assertEquals("\" \t \n \r \\ / \b \f", toml.getString("key"));
|
||||
assertEquals("\" \t \n \r \\ \b \f", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -85,6 +85,11 @@ public class StringTest {
|
|||
new Toml().parse("key=\"\\m\"");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_escaped_slash() throws Exception {
|
||||
new Toml().parse("key=\"\\/\"");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_text_after_literal_string() {
|
||||
new Toml().parse("a = ' ' jdkf");
|
||||
|
@ -96,10 +101,15 @@ public class StringTest {
|
|||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_unterminated_multiline_literal_string() throws Exception {
|
||||
public void should_fail_on_multiline_literal_string_with_malformed_comment() throws Exception {
|
||||
new Toml().parse("a = '''some\n text\n''\nb = '''1'''");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_unterminated_multiline_literal_string() throws Exception {
|
||||
new Toml().parse("a = '''some\n text\n''");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_unterminated_multiline_literal_string_on_single_line() throws Exception {
|
||||
new Toml().parse("a = '''some text''");
|
||||
|
|
|
@ -87,6 +87,11 @@ public class TableArrayTest {
|
|||
assertEquals("granny smith", appleVariety.getString("name"));
|
||||
assertEquals("plantain", bananaVariety);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_empty_table_array_name() {
|
||||
new Toml().parse("[[]]");
|
||||
}
|
||||
|
||||
private File file(String fileName) {
|
||||
return new File(getClass().getResource(fileName + ".toml").getFile());
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.fest.reflect.core.Reflection;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -60,6 +59,17 @@ public class TomlTest {
|
|||
assertEquals(2, toml.getLong("a.d").intValue());
|
||||
assertEquals(1, toml.getTable("a.b").getLong("c").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_handle_navigation_to_missing_value() throws Exception {
|
||||
Toml toml = new Toml();
|
||||
|
||||
assertNull(toml.getString("a.b"));
|
||||
assertNull(toml.getString("a.b[0].c"));
|
||||
assertThat(toml.getList("a.b"), hasSize(0));
|
||||
assertTrue(toml.getTable("a.b").isEmpty());
|
||||
assertTrue(toml.getTable("a.b[0]").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_null_if_no_value_for_key() throws Exception {
|
||||
|
@ -72,7 +82,7 @@ public class TomlTest {
|
|||
public void should_return_empty_list_if_no_value_for_key() throws Exception {
|
||||
Toml toml = new Toml().parse("");
|
||||
|
||||
assertTrue(toml.getList("a", String.class).isEmpty());
|
||||
assertTrue(toml.<String>getList("a").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,14 +92,6 @@ public class TomlTest {
|
|||
assertNull(toml.getString("group.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_empty_toml_when_no_value_for_table() throws Exception {
|
||||
Toml toml = new Toml().parse("[a]").getTable("b");
|
||||
|
||||
assertTrue(Reflection.field("values").ofType(Map.class).in(toml).get().isEmpty());
|
||||
assertNull(toml.getString("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_load_from_file() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_load_from_file.toml").getFile()));
|
||||
|
@ -97,70 +99,6 @@ public class TomlTest {
|
|||
assertEquals("value", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a1 = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a1").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group1]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group1.a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a_a = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a_a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_question_marks_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("key?=true");
|
||||
|
||||
assertTrue(toml.getBoolean("key?"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_dots_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse(file("should_support_dots_in_key_names"));
|
||||
|
||||
assertEquals(1, toml.getLong("a").intValue());
|
||||
assertEquals(2, toml.getLong("b.c").intValue());
|
||||
assertEquals(3, toml.getTable("b").getLong("c").intValue());
|
||||
assertEquals(4, toml.getLong("b.a.b").intValue());
|
||||
assertEquals(5, toml.getLong("d.e.a").intValue());
|
||||
assertEquals(6, toml.getLong("d.e.a.b.c").intValue());
|
||||
assertEquals(6, toml.getTable("d.e").getLong("a.b.c").intValue());
|
||||
assertEquals(7, toml.getTables("f").get(0).getLong("a.b").intValue());
|
||||
assertEquals(8, toml.getLong("f[1].a.b").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group_a]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group_a.a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_sharp_sign_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group#]\nkey=1");
|
||||
|
||||
assertEquals(1, toml.getLong("group#.key").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_spaces_in_table_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[valid key]");
|
||||
|
||||
assertNotNull(toml.getTable("valid key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_blank_lines() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_support_blank_line.toml").getFile()));
|
||||
|
@ -180,7 +118,7 @@ public class TomlTest {
|
|||
cal.set(Calendar.MILLISECOND, 0);
|
||||
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
assertEquals(cal.getTime(), toml.getDate("d"));
|
||||
assertThat(toml.getList("e", String.class), Matchers.contains("a", "b"));
|
||||
assertThat(toml.<String>getList("e"), Matchers.contains("a", "b"));
|
||||
assertTrue(toml.getBoolean("f"));
|
||||
assertEquals("abc", toml.getString("g"));
|
||||
assertEquals("abc", toml.getString("h"));
|
||||
|
@ -188,6 +126,15 @@ public class TomlTest {
|
|||
assertEquals("abc\nabc", toml.getString("j"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_be_empty_if_no_values() throws Exception {
|
||||
assertTrue(new Toml().isEmpty());
|
||||
Toml toml = new Toml().parse("[a]");
|
||||
assertTrue(toml.getTable("a").isEmpty());
|
||||
assertTrue(toml.getTable("b").isEmpty());
|
||||
assertFalse(toml.isEmpty());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_empty_key_name() throws Exception {
|
||||
new Toml().parse(" = 1");
|
||||
|
@ -222,28 +169,4 @@ public class TomlTest {
|
|||
public void should_fail_when_illegal_characters_after_table() throws Exception {
|
||||
new Toml().parse("[error] if you didn't catch this, your parser is broken");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_empty_table_name() {
|
||||
new Toml().parse("[]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_compound_table_name_ending_with_empty_table_name() {
|
||||
new Toml().parse("[a.]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_compound_table_name_containing_empty_table_name() {
|
||||
new Toml().parse("[a..b]");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_compound_table_name_starting_with_empty_table_name() {
|
||||
new Toml().parse("[.b]");
|
||||
}
|
||||
|
||||
private File file(String file) {
|
||||
return new File(getClass().getResource(file + ".toml").getFile());
|
||||
}
|
||||
}
|
||||
|
|
22
src/test/java/com/moandjiezana/toml/UnicodeTest.java
Normal file
22
src/test/java/com/moandjiezana/toml/UnicodeTest.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class UnicodeTest {
|
||||
|
||||
@Test
|
||||
public void should_support_short_escape_form() throws Exception {
|
||||
Toml toml = new Toml().parse("key = \"Jos\u00E9\\nLocation\tSF\"");
|
||||
|
||||
assertEquals("José\nLocation\tSF", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_unicode_literal() throws Exception {
|
||||
Toml toml = new Toml().parse("key = \"José LöcÄtion SF\"");
|
||||
|
||||
assertEquals("José LöcÄtion SF", toml.getString("key"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ab": {"type": "integer", "value": "1"}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ab = 1
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"_-1234567890": {
|
||||
"type": "integer", "value": "1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
_-1234567890 = 1
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"plain": {"type": "integer", "value": "1"},
|
||||
"\"with.dot\"": {"type": "integer", "value": "2"},
|
||||
"plain_table": {
|
||||
"plain": {"type": "integer", "value": "3"},
|
||||
"\"with.dot\"": {"type": "integer", "value": "4"}
|
||||
},
|
||||
"table": {
|
||||
"withdot": {
|
||||
"plain": {"type": "integer", "value": "5"},
|
||||
"\"key.with.dots\"": {"type": "integer", "value": "6"}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
plain = 1
|
||||
"with.dot" = 2
|
||||
|
||||
[plain_table]
|
||||
plain = 3
|
||||
"with.dot" = 4
|
||||
|
||||
[table.withdot]
|
||||
plain = 5
|
||||
"key.with.dots" = 6
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"multiline_empty_one": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"multiline_empty_two": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"multiline_empty_three": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"multiline_empty_four": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"equivalent_one": {
|
||||
"type": "string",
|
||||
"value": "The quick brown fox jumps over the lazy dog."
|
||||
},
|
||||
"equivalent_two": {
|
||||
"type": "string",
|
||||
"value": "The quick brown fox jumps over the lazy dog."
|
||||
},
|
||||
"equivalent_three": {
|
||||
"type": "string",
|
||||
"value": "The quick brown fox jumps over the lazy dog."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
multiline_empty_one = """"""
|
||||
multiline_empty_two = """
|
||||
"""
|
||||
multiline_empty_three = """\
|
||||
"""
|
||||
multiline_empty_four = """\
|
||||
\
|
||||
\
|
||||
"""
|
||||
|
||||
equivalent_one = "The quick brown fox jumps over the lazy dog."
|
||||
equivalent_two = """
|
||||
The quick brown \
|
||||
|
||||
|
||||
fox jumps over \
|
||||
the lazy dog."""
|
||||
|
||||
equivalent_three = """\
|
||||
The quick brown \
|
||||
fox jumps over \
|
||||
the lazy dog.\
|
||||
"""
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"backspace": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u0008 backspace character."
|
||||
},
|
||||
"tab": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u0009 tab character."
|
||||
},
|
||||
"newline": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u000A new line character."
|
||||
},
|
||||
"formfeed": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u000C form feed character."
|
||||
},
|
||||
"carriage": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u000D carriage return character."
|
||||
},
|
||||
"quote": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u0022 quote character."
|
||||
},
|
||||
"backslash": {
|
||||
"type": "string",
|
||||
"value": "This string has a \u005C backslash character."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
backspace = "This string has a \b backspace character."
|
||||
tab = "This string has a \t tab character."
|
||||
newline = "This string has a \n new line character."
|
||||
formfeed = "This string has a \f form feed character."
|
||||
carriage = "This string has a \r carriage return character."
|
||||
quote = "This string has a \" quote character."
|
||||
backslash = "This string has a \\ backslash character."
|
182
src/test/resources/com/moandjiezana/toml/example-v0.3.0.toml
Normal file
182
src/test/resources/com/moandjiezana/toml/example-v0.3.0.toml
Normal file
|
@ -0,0 +1,182 @@
|
|||
# Comment
|
||||
# I am a comment. Hear me roar. Roar.
|
||||
|
||||
# Table
|
||||
# Tables (also known as hash tables or dictionaries) are collections of key/value pairs.
|
||||
# They appear in square brackets on a line by themselves.
|
||||
|
||||
[Table]
|
||||
|
||||
key = "value" # Yeah, you can do this.
|
||||
|
||||
# Nested tables are denoted by table names with dots in them. Name your tables whatever crap you please, just don't use #, ., [ or ].
|
||||
|
||||
[dog.tater]
|
||||
type = "pug"
|
||||
|
||||
# You don't need to specify all the super-tables if you don't want to. TOML knows how to do it for you.
|
||||
|
||||
# [x] you
|
||||
# [x.y] don't
|
||||
# [x.y.z] need these
|
||||
[x.y.z.w] # for this to work
|
||||
|
||||
# String
|
||||
# There are four ways to express strings: basic, multi-line basic, literal, and multi-line literal.
|
||||
# All strings must contain only valid UTF-8 characters.
|
||||
|
||||
[String]
|
||||
basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
|
||||
|
||||
[String.Multiline]
|
||||
|
||||
# The following strings are byte-for-byte equivalent:
|
||||
key1 = "One\nTwo"
|
||||
key2 = """One\nTwo"""
|
||||
key3 = """
|
||||
One
|
||||
Two"""
|
||||
|
||||
[String.Multilined.Singleline]
|
||||
|
||||
# The following strings are byte-for-byte equivalent:
|
||||
key1 = "The quick brown fox jumps over the lazy dog."
|
||||
|
||||
key2 = """
|
||||
The quick brown \
|
||||
|
||||
|
||||
fox jumps over \
|
||||
the lazy dog."""
|
||||
|
||||
key3 = """\
|
||||
The quick brown \
|
||||
fox jumps over \
|
||||
the lazy dog.\
|
||||
"""
|
||||
|
||||
[String.Literal]
|
||||
|
||||
# What you see is what you get.
|
||||
winpath = 'C:\Users\nodejs\templates'
|
||||
winpath2 = '\\ServerX\admin$\system32\'
|
||||
quoted = 'Tom "Dubs" Preston-Werner'
|
||||
regex = '<\i\c*\s*>'
|
||||
|
||||
|
||||
[String.Literal.Multiline]
|
||||
|
||||
regex2 = '''I [dw]on't need \d{2} apples'''
|
||||
lines = '''
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
'''
|
||||
|
||||
# Integer
|
||||
# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
|
||||
# Negative numbers are prefixed with a minus sign.
|
||||
|
||||
[Integer]
|
||||
key1 = +99
|
||||
key2 = 42
|
||||
key3 = 0
|
||||
key4 = -17
|
||||
|
||||
# Float
|
||||
# A float consists of an integer part (which may be prefixed with a plus or minus sign)
|
||||
# followed by a fractional part and/or an exponent part.
|
||||
|
||||
[Float.fractional]
|
||||
|
||||
# fractional
|
||||
key1 = +1.0
|
||||
key2 = 3.1415
|
||||
key3 = -0.01
|
||||
|
||||
[Float.exponent]
|
||||
|
||||
# exponent
|
||||
key1 = 5e+22
|
||||
key2 = 1e6
|
||||
key3 = -2E-2
|
||||
|
||||
[Float.both]
|
||||
|
||||
# both
|
||||
key = 6.626e-34
|
||||
|
||||
# Boolean
|
||||
# Booleans are just the tokens you're used to. Always lowercase.
|
||||
|
||||
[Booleans]
|
||||
True = true
|
||||
False = false
|
||||
|
||||
# Datetime
|
||||
# Datetimes are RFC 3339 dates.
|
||||
|
||||
[Datetime]
|
||||
key1 = 1979-05-27T07:32:00Z
|
||||
key2 = 1979-05-27T00:32:00-07:00
|
||||
key3 = 1979-05-27T00:32:00.999-07:00
|
||||
|
||||
# Array
|
||||
# Arrays are square brackets with other primitives inside. Whitespace is ignored. Elements are separated by commas. Data types may not be mixed.
|
||||
|
||||
[Array]
|
||||
key1 = [ 1, 2, 3 ]
|
||||
key2 = [ "red", "yellow", "green" ]
|
||||
key3 = [ [ 1, 2 ], [3, 4, 5] ]
|
||||
key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
|
||||
|
||||
#Arrays can also be multiline. So in addition to ignoring whitespace, arrays also ignore newlines between the brackets.
|
||||
# Terminating commas are ok before the closing bracket.
|
||||
|
||||
key5 = [
|
||||
1, 2, 3
|
||||
]
|
||||
key6 = [
|
||||
1,
|
||||
2, # this is ok
|
||||
]
|
||||
|
||||
# Array of Tables
|
||||
# These can be expressed by using a table name in double brackets.
|
||||
# Each table with the same double bracketed name will be an element in the array.
|
||||
# The tables are inserted in the order encountered.
|
||||
|
||||
[[products]]
|
||||
name = "Hammer"
|
||||
sku = 738594937
|
||||
|
||||
[[products]]
|
||||
|
||||
[[products]]
|
||||
name = "Nail"
|
||||
sku = 284758393
|
||||
color = "gray"
|
||||
|
||||
|
||||
# You can create nested arrays of tables as well.
|
||||
|
||||
[[fruit]]
|
||||
name = "apple"
|
||||
|
||||
[fruit.physical]
|
||||
color = "red"
|
||||
shape = "round"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "red delicious"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "granny smith"
|
||||
|
||||
[[fruit]]
|
||||
name = "banana"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "plantain"
|
||||
|
|
@ -13,8 +13,8 @@ test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
|||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
||||
# Things will get harder
|
||||
|
||||
[the.hard.bit#]
|
||||
what? = "You don't think some user won't do that?"
|
||||
[the.hard."bit#"]
|
||||
"what?" = "You don't think some user won't do that?"
|
||||
multi_line_array = [
|
||||
"]",
|
||||
# ] Oh yes I did
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
points = [ { x = 1, y = 2, z = 3 },
|
||||
{ x = 7, y = 8, z = 9 },
|
||||
{ x = 2, y = 4, z = 8 },
|
||||
{ x = "3", y = "6", z = "12" }
|
||||
]
|
|
@ -1,16 +0,0 @@
|
|||
a = 1
|
||||
b.c = 2
|
||||
|
||||
[b]
|
||||
c = 3
|
||||
a.b = 4
|
||||
|
||||
[d.e]
|
||||
a = 5
|
||||
a.b.c = 6
|
||||
|
||||
[[f]]
|
||||
a.b = 7
|
||||
|
||||
[[f]]
|
||||
a.b = 8
|
|
@ -1,3 +1,3 @@
|
|||
key = "\" \t \n \r \\ \/ \b \f"
|
||||
key = "\" \t \n \r \\ \b \f"
|
||||
unicode_key = "more or less \u00B1"
|
||||
unicode_key_uppercase = "more or less \U00B1"
|
||||
|
|
Loading…
Reference in a new issue