mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add toml support
This commit is contained in:
parent
a30d5bb1a1
commit
42853b6706
@ -25,7 +25,7 @@ public final class FastStringWriter extends Writer {
|
||||
|
||||
private static final int DEFAULT_CAPACITY = 16;
|
||||
|
||||
private final StringBuilder builder;
|
||||
private final StringBuilder stringBuilder;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -43,31 +43,51 @@ public final class FastStringWriter extends Writer {
|
||||
if (initialSize < 0) {
|
||||
initialSize = DEFAULT_CAPACITY;
|
||||
}
|
||||
this.builder = new StringBuilder(initialSize);
|
||||
this.stringBuilder = new StringBuilder(initialSize);
|
||||
}
|
||||
|
||||
// region ----- append
|
||||
@Override
|
||||
public FastStringWriter append(final char c) {
|
||||
this.stringBuilder.append(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastStringWriter append(final CharSequence csq, final int start, final int end) {
|
||||
this.stringBuilder.append(csq, start, end);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastStringWriter append(final CharSequence csq) {
|
||||
this.stringBuilder.append(csq);
|
||||
return this;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- write
|
||||
@Override
|
||||
public void write(final int c) {
|
||||
this.builder.append((char) c);
|
||||
this.stringBuilder.append((char) c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final String str) {
|
||||
this.builder.append(str);
|
||||
this.stringBuilder.append(str);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final String str, final int off, final int len) {
|
||||
this.builder.append(str, off, off + len);
|
||||
this.stringBuilder.append(str, off, off + len);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final char[] cbuf) {
|
||||
this.builder.append(cbuf, 0, cbuf.length);
|
||||
this.stringBuilder.append(cbuf, 0, cbuf.length);
|
||||
}
|
||||
|
||||
|
||||
@ -79,8 +99,9 @@ public final class FastStringWriter extends Writer {
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
this.builder.append(cbuf, off, len);
|
||||
this.stringBuilder.append(cbuf, off, len);
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
||||
@Override
|
||||
@ -97,7 +118,7 @@ public final class FastStringWriter extends Writer {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.builder.toString();
|
||||
return this.stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import cn.hutool.core.io.resource.NoResourceException;
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import cn.hutool.setting.Setting;
|
||||
import cn.hutool.setting.SettingRuntimeException;
|
||||
import cn.hutool.setting.SettingException;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@ -58,7 +58,7 @@ public class CronUtil {
|
||||
public static void setCronSetting(final String cronSettingPath) {
|
||||
try {
|
||||
crontabSetting = new Setting(cronSettingPath, Setting.DEFAULT_CHARSET, false);
|
||||
} catch (final SettingRuntimeException | NoResourceException e) {
|
||||
} catch (final SettingException | NoResourceException e) {
|
||||
// ignore setting file parse error and no config error
|
||||
}
|
||||
}
|
||||
|
@ -43,10 +43,11 @@
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<!-- YAML支持 -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.32</version>
|
||||
<version>2.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -19,30 +19,30 @@ import cn.hutool.core.text.StrUtil;
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class SettingRuntimeException extends RuntimeException {
|
||||
public class SettingException extends RuntimeException {
|
||||
private static final long serialVersionUID = 7941096116780378387L;
|
||||
|
||||
public SettingRuntimeException(final Throwable e) {
|
||||
public SettingException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public SettingRuntimeException(final String message) {
|
||||
public SettingException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SettingRuntimeException(final String messageTemplate, final Object... params) {
|
||||
public SettingException(final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public SettingRuntimeException(final String message, final Throwable throwable) {
|
||||
public SettingException(final String message, final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public SettingRuntimeException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
public SettingException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public SettingRuntimeException(final Throwable throwable, final String messageTemplate, final Object... params) {
|
||||
public SettingException(final Throwable throwable, final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package cn.hutool.setting.toml;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
|
||||
public class Toml {
|
||||
/**
|
||||
* A DateTimeFormatter that uses the TOML format.
|
||||
*/
|
||||
public static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder()
|
||||
.append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.optionalStart()
|
||||
.appendOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter();
|
||||
}
|
@ -0,0 +1,676 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package cn.hutool.setting.toml;
|
||||
|
||||
import cn.hutool.setting.SettingException;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TOML文件读取
|
||||
* <h1>DateTimes support</h1>
|
||||
* <p>
|
||||
* The datetime support is more extended than in the TOML specification. This reader supports three kind of datetimes:
|
||||
* <ol>
|
||||
* <li>Full RFC 3339. Examples: 1979-05-27T07:32:00Z, 1979-05-27T00:32:00-07:00, 1979-05-27T00:32:00.999999-07:00</li>
|
||||
* <li>Without local offset. Examples: 1979-05-27T07:32:00, 1979-05-27T00:32:00.999999</li>
|
||||
* <li>Without time (just the date). Example: 2015-03-20</li>
|
||||
* </ol>
|
||||
* Moreover, parsing datetimes gives different objects according to the informations provided. For example, 2015-03-20
|
||||
* is parsed as a {@link LocalDate}, 2015-03-20T19:04:35 as a {@link LocalDateTime}, and 2015-03-20T19:04:35+01:00 as a
|
||||
* {@link ZonedDateTime}.
|
||||
* </p>
|
||||
* <h1>Lenient bare keys</h1>
|
||||
* <p>
|
||||
* This library allows "lenient" bare keys by default, as opposite to the "strict" bare keys required by the TOML
|
||||
* specification. Strict bare keys may only contain letters, numbers, underscores, and dashes (A-Za-z0-9_-). Lenient
|
||||
* bare keys may contain any character except those below the space character ' ' in the unicode table, '.', '[', ']'
|
||||
* and '='. The behaviour of TomlReader regarding bare keys is set in its constructor.
|
||||
* </p>
|
||||
*
|
||||
* @author TheElectronWill
|
||||
*
|
||||
*/
|
||||
public class TomlReader {
|
||||
|
||||
private final String data;
|
||||
private final boolean strictAsciiBareKeys;
|
||||
private int pos = 0;// current position
|
||||
private int line = 1;// current line
|
||||
|
||||
/**
|
||||
* Creates a new TomlReader.
|
||||
*
|
||||
* @param data the TOML data to read
|
||||
* @param strictAsciiBareKeys <code>true</false> to allow only strict bare keys, {@code false} to allow lenient ones.
|
||||
*/
|
||||
public TomlReader(final String data, final boolean strictAsciiBareKeys) {
|
||||
this.data = data;
|
||||
this.strictAsciiBareKeys = strictAsciiBareKeys;
|
||||
}
|
||||
|
||||
private boolean hasNext() {
|
||||
return pos < data.length();
|
||||
}
|
||||
|
||||
private char next() {
|
||||
return data.charAt(pos++);
|
||||
}
|
||||
|
||||
private char nextUseful(final boolean skipComments) {
|
||||
char c = ' ';
|
||||
while (hasNext() && (c == ' ' || c == '\t' || c == '\r' || c == '\n' || (c == '#' && skipComments))) {
|
||||
c = next();
|
||||
if (skipComments && c == '#') {
|
||||
final int nextLinebreak = data.indexOf('\n', pos);
|
||||
if (nextLinebreak == -1) {
|
||||
pos = data.length();
|
||||
} else {
|
||||
pos = nextLinebreak + 1;
|
||||
line++;
|
||||
}
|
||||
} else if (c == '\n') {
|
||||
line++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private char nextUsefulOrLinebreak() {
|
||||
char c = ' ';
|
||||
while (c == ' ' || c == '\t' || c == '\r') {
|
||||
if (!hasNext())// fixes error when no '\n' at the end of the file
|
||||
return '\n';
|
||||
c = next();
|
||||
}
|
||||
if (c == '\n')
|
||||
line++;
|
||||
return c;
|
||||
}
|
||||
|
||||
private Object nextValue(final char firstChar) {
|
||||
switch (firstChar) {
|
||||
case '+':
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return nextNumberOrDate(firstChar);
|
||||
case '"':
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '"' && c3 == '"') {
|
||||
pos += 2;
|
||||
return nextBasicMultilineString();
|
||||
}
|
||||
}
|
||||
return nextBasicString();
|
||||
case '\'':
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '\'' && c3 == '\'') {
|
||||
pos += 2;
|
||||
return nextLiteralMultilineString();
|
||||
}
|
||||
}
|
||||
return nextLiteralString();
|
||||
case '[':
|
||||
return nextArray();
|
||||
case '{':
|
||||
return nextInlineTable();
|
||||
case 't':// Must be "true"
|
||||
if (pos + 3 > data.length() || next() != 'r' || next() != 'u' || next() != 'e') {
|
||||
throw new SettingException("Invalid value at line " + line);
|
||||
}
|
||||
return true;
|
||||
case 'f':// Must be "false"
|
||||
if (pos + 4 > data.length() || next() != 'a' || next() != 'l' || next() != 's' || next() != 'e') {
|
||||
throw new SettingException("Invalid value at line " + line);
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
throw new SettingException("Invalid character '" + toString(firstChar) + "' at line " + line);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> read() {
|
||||
final Map<String, Object> map = nextTableContent();
|
||||
|
||||
if (!hasNext() && pos > 0 && data.charAt(pos - 1) == '[')
|
||||
throw new SettingException("Invalid table declaration at line " + line + ": it never ends");
|
||||
|
||||
while (hasNext()) {
|
||||
char c = nextUseful(true);
|
||||
final boolean twoBrackets;
|
||||
if (c == '[') {
|
||||
twoBrackets = true;
|
||||
c = nextUseful(false);
|
||||
} else {
|
||||
twoBrackets = false;
|
||||
}
|
||||
pos--;
|
||||
|
||||
// --- Reads the key --
|
||||
final List<String> keyParts = new ArrayList<>(4);
|
||||
boolean insideSquareBrackets = true;
|
||||
while (insideSquareBrackets) {
|
||||
if (!hasNext())
|
||||
throw new SettingException("Invalid table declaration at line " + line + ": it never ends");
|
||||
|
||||
String name = null;
|
||||
final char nameFirstChar = nextUseful(false);
|
||||
switch (nameFirstChar) {
|
||||
case '"': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '"' && c3 == '"') {
|
||||
pos += 2;
|
||||
name = nextBasicMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = nextBasicString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\'': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '\'' && c3 == '\'') {
|
||||
pos += 2;
|
||||
name = nextLiteralMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = nextLiteralString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pos--;// to include the first (already read) non-space character
|
||||
name = nextBareKey(']', '.').trim();
|
||||
if (data.charAt(pos) == ']') {
|
||||
if (!name.isEmpty())
|
||||
keyParts.add(name);
|
||||
insideSquareBrackets = false;
|
||||
} else if (name.isEmpty()) {
|
||||
throw new SettingException("Invalid empty key at line " + line);
|
||||
}
|
||||
|
||||
pos++;// to go after the character we stopped at in nextBareKey()
|
||||
break;
|
||||
}
|
||||
if (insideSquareBrackets)
|
||||
keyParts.add(name.trim());
|
||||
}
|
||||
|
||||
// -- Checks --
|
||||
if (keyParts.isEmpty())
|
||||
throw new SettingException("Invalid empty key at line " + line);
|
||||
|
||||
if (twoBrackets && next() != ']') {// 2 brackets at the start but only one at the end!
|
||||
throw new SettingException("Missing character ']' at line " + line);
|
||||
}
|
||||
|
||||
// -- Reads the value (table content) --
|
||||
final Map<String, Object> value = nextTableContent();
|
||||
|
||||
// -- Saves the value --
|
||||
Map<String, Object> valueMap = map;// the map that contains the value
|
||||
for (int i = 0; i < keyParts.size() - 1; i++) {
|
||||
final String part = keyParts.get(i);
|
||||
final Object child = valueMap.get(part);
|
||||
final Map<String, Object> childMap;
|
||||
if (child == null) {// implicit table
|
||||
childMap = new HashMap<>(4);
|
||||
valueMap.put(part, childMap);
|
||||
} else if (child instanceof Map) {// table
|
||||
childMap = (Map) child;
|
||||
} else {// array
|
||||
final List<Map> list = (List) child;
|
||||
childMap = list.get(list.size() - 1);
|
||||
}
|
||||
valueMap = childMap;
|
||||
}
|
||||
if (twoBrackets) {// element of a table array
|
||||
final String name = keyParts.get(keyParts.size() - 1);
|
||||
Collection<Map> tableArray = (Collection) valueMap.get(name);
|
||||
if (tableArray == null) {
|
||||
tableArray = new ArrayList<>(2);
|
||||
valueMap.put(name, tableArray);
|
||||
}
|
||||
tableArray.add(value);
|
||||
} else {// just a table
|
||||
valueMap.put(keyParts.get(keyParts.size() - 1), value);
|
||||
}
|
||||
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List nextArray() {
|
||||
final ArrayList<Object> list = new ArrayList<>();
|
||||
while (true) {
|
||||
final char c = nextUseful(true);
|
||||
if (c == ']') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
final Object value = nextValue(c);
|
||||
if (!list.isEmpty() && !(list.get(0).getClass().isAssignableFrom(value.getClass())))
|
||||
throw new SettingException("Invalid array at line " + line + ": all the values must have the same type");
|
||||
list.add(value);
|
||||
|
||||
final char afterEntry = nextUseful(true);
|
||||
if (afterEntry == ']') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
if (afterEntry != ',') {
|
||||
throw new SettingException("Invalid array at line " + line + ": expected a comma after each value");
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
list.trimToSize();
|
||||
return list;
|
||||
}
|
||||
|
||||
private Map<String, Object> nextInlineTable() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
while (true) {
|
||||
final char nameFirstChar = nextUsefulOrLinebreak();
|
||||
String name = null;
|
||||
switch (nameFirstChar) {
|
||||
case '}':
|
||||
return map;
|
||||
case '"': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '"' && c3 == '"') {
|
||||
pos += 2;
|
||||
name = nextBasicMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null)
|
||||
name = nextBasicString();
|
||||
break;
|
||||
}
|
||||
case '\'': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '\'' && c3 == '\'') {
|
||||
pos += 2;
|
||||
name = nextLiteralMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null)
|
||||
name = nextLiteralString();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pos--;// to include the first (already read) non-space character
|
||||
name = nextBareKey(' ', '\t', '=');
|
||||
if (name.isEmpty())
|
||||
throw new SettingException("Invalid empty key at line " + line);
|
||||
break;
|
||||
}
|
||||
|
||||
final char separator = nextUsefulOrLinebreak();// tries to find the '=' sign
|
||||
if (separator != '=')
|
||||
throw new SettingException("Invalid character '" + toString(separator) + "' at line " + line + ": expected '='");
|
||||
|
||||
final char valueFirstChar = nextUsefulOrLinebreak();
|
||||
final Object value = nextValue(valueFirstChar);
|
||||
map.put(name, value);
|
||||
|
||||
final char after = nextUsefulOrLinebreak();
|
||||
if (after == '}' || !hasNext()) {
|
||||
return map;
|
||||
} else if (after != ',') {
|
||||
throw new SettingException("Invalid inline table at line " + line + ": missing comma");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> nextTableContent() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
while (true) {
|
||||
final char nameFirstChar = nextUseful(true);
|
||||
if (!hasNext() || nameFirstChar == '[') {
|
||||
return map;
|
||||
}
|
||||
String name = null;
|
||||
switch (nameFirstChar) {
|
||||
case '"': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '"' && c3 == '"') {
|
||||
pos += 2;
|
||||
name = nextBasicMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = nextBasicString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\'': {
|
||||
if (pos + 1 < data.length()) {
|
||||
final char c2 = data.charAt(pos);
|
||||
final char c3 = data.charAt(pos + 1);
|
||||
if (c2 == '\'' && c3 == '\'') {
|
||||
pos += 2;
|
||||
name = nextLiteralMultilineString();
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = nextLiteralString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pos--;// to include the first (already read) non-space character
|
||||
name = nextBareKey(' ', '\t', '=');
|
||||
if (name.isEmpty())
|
||||
throw new SettingException("Invalid empty key at line " + line);
|
||||
break;
|
||||
}
|
||||
final char separator = nextUsefulOrLinebreak();// tries to find the '=' sign
|
||||
if (separator != '=')// an other character
|
||||
throw new SettingException("Invalid character '" + toString(separator) + "' at line " + line + ": expected '='");
|
||||
|
||||
final char valueFirstChar = nextUsefulOrLinebreak();
|
||||
if (valueFirstChar == '\n') {
|
||||
throw new SettingException("Invalid newline before the value at line " + line);
|
||||
}
|
||||
final Object value = nextValue(valueFirstChar);
|
||||
|
||||
final char afterEntry = nextUsefulOrLinebreak();
|
||||
if (afterEntry == '#') {
|
||||
pos--;// to make the next nextUseful() call read the # character
|
||||
} else if (afterEntry != '\n') {
|
||||
throw new SettingException("Invalid character '" + toString(afterEntry) + "' after the value at line " + line);
|
||||
}
|
||||
if (map.containsKey(name))
|
||||
throw new SettingException("Duplicate key \"" + name + "\"");
|
||||
|
||||
map.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private Object nextNumberOrDate(final char first) {
|
||||
boolean maybeDouble = true, maybeInteger = true, maybeDate = true;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(first);
|
||||
char c;
|
||||
whileLoop: while (hasNext()) {
|
||||
c = next();
|
||||
switch (c) {
|
||||
case ':':
|
||||
case 'T':
|
||||
case 'Z':
|
||||
maybeInteger = maybeDouble = false;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
maybeInteger = maybeDate = false;
|
||||
break;
|
||||
case '.':
|
||||
maybeInteger = false;
|
||||
break;
|
||||
case '-':
|
||||
if (pos != 0 && data.charAt(pos - 1) != 'e' && data.charAt(pos - 1) != 'E')
|
||||
maybeInteger = maybeDouble = false;
|
||||
break;
|
||||
case ',':
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case ']':
|
||||
case '}':
|
||||
pos--;
|
||||
break whileLoop;
|
||||
}
|
||||
if (c == '_')
|
||||
maybeDate = false;
|
||||
else
|
||||
sb.append(c);
|
||||
}
|
||||
final String valueStr = sb.toString();
|
||||
try {
|
||||
if (maybeInteger) {
|
||||
if (valueStr.length() < 10)
|
||||
return Integer.parseInt(valueStr);
|
||||
return Long.parseLong(valueStr);
|
||||
}
|
||||
|
||||
if (maybeDouble)
|
||||
return Double.parseDouble(valueStr);
|
||||
|
||||
if (maybeDate)
|
||||
return Toml.DATE_FORMATTER.parseBest(valueStr, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
|
||||
|
||||
} catch (final Exception ex) {
|
||||
throw new SettingException("Invalid value: \"" + valueStr + "\" at line " + line, ex);
|
||||
}
|
||||
|
||||
throw new SettingException("Invalid value: \"" + valueStr + "\" at line " + line);
|
||||
}
|
||||
|
||||
private String nextBareKey(final char... allowedEnds) {
|
||||
final String keyName;
|
||||
for (int i = pos; i < data.length(); i++) {
|
||||
final char c = data.charAt(i);
|
||||
for (final char allowedEnd : allowedEnds) {
|
||||
if (c == allowedEnd) {// checks if this character allowed to end this bare key
|
||||
keyName = data.substring(pos, i);
|
||||
pos = i;
|
||||
return keyName;
|
||||
}
|
||||
}
|
||||
if (strictAsciiBareKeys) {
|
||||
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'))
|
||||
throw new SettingException("Forbidden character '" + toString(c) + "' in strict bare-key at line " + line);
|
||||
} else if (c <= ' ' || c == '#' || c == '=' || c == '.' || c == '[' || c == ']') {// lenient bare key
|
||||
throw new SettingException("Forbidden character '" + toString(c) + "' in lenient bare-key at line " + line);
|
||||
} // else continue reading
|
||||
}
|
||||
throw new SettingException(
|
||||
"Invalid key/value pair at line " + line + " end of data reached before the value attached to the key was found");
|
||||
}
|
||||
|
||||
private String nextLiteralString() {
|
||||
final int index = data.indexOf('\'', pos);
|
||||
if (index == -1)
|
||||
throw new SettingException("Invalid literal String at line " + line + ": it never ends");
|
||||
|
||||
final String str = data.substring(pos, index);
|
||||
if (str.indexOf('\n') != -1)
|
||||
throw new SettingException("Invalid literal String at line " + line + ": newlines are not allowed here");
|
||||
|
||||
pos = index + 1;
|
||||
return str;
|
||||
}
|
||||
|
||||
private String nextLiteralMultilineString() {
|
||||
final int index = data.indexOf("'''", pos);
|
||||
if (index == -1)
|
||||
throw new SettingException("Invalid multiline literal String at line " + line + ": it never ends");
|
||||
final String str;
|
||||
if (data.charAt(pos) == '\r' && data.charAt(pos + 1) == '\n') {// "\r\n" at the beginning of the string
|
||||
str = data.substring(pos + 2, index);
|
||||
line++;
|
||||
} else if (data.charAt(pos) == '\n') {// '\n' at the beginning of the string
|
||||
str = data.substring(pos + 1, index);
|
||||
line++;
|
||||
} else {
|
||||
str = data.substring(pos, index);
|
||||
}
|
||||
for (int i = 0; i < str.length(); i++) {// count lines
|
||||
final char c = str.charAt(i);
|
||||
if (c == '\n')
|
||||
line++;
|
||||
}
|
||||
pos = index + 3;// goes after the 3 quotes
|
||||
return str;
|
||||
}
|
||||
|
||||
private String nextBasicString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean escape = false;
|
||||
while (hasNext()) {
|
||||
final char c = next();
|
||||
if (c == '\n' || c == '\r')
|
||||
throw new SettingException("Invalid basic String at line " + line + ": newlines not allowed");
|
||||
if (escape) {
|
||||
sb.append(unescape(c));
|
||||
escape = false;
|
||||
} else if (c == '\\') {
|
||||
escape = true;
|
||||
} else if (c == '"') {
|
||||
return sb.toString();
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
throw new SettingException("Invalid basic String at line " + line + ": it nerver ends");
|
||||
}
|
||||
|
||||
private String nextBasicMultilineString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean first = true, escape = false;
|
||||
while (hasNext()) {
|
||||
final char c = next();
|
||||
if (first && (c == '\r' || c == '\n')) {
|
||||
if (c == '\r' && hasNext() && data.charAt(pos) == '\n')// "\r\n"
|
||||
pos++;// so that it is NOT read by the next call to next()
|
||||
else
|
||||
line++;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (escape) {
|
||||
if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
|
||||
if (c == '\r' && hasNext() && data.charAt(pos) == '\n')// "\r\n"
|
||||
pos++;
|
||||
else if (c == '\n')
|
||||
line++;
|
||||
nextUseful(false);
|
||||
pos--;// so that it is read by the next call to next()
|
||||
} else {
|
||||
sb.append(unescape(c));
|
||||
}
|
||||
escape = false;
|
||||
} else if (c == '\\') {
|
||||
escape = true;
|
||||
} else if (c == '"') {
|
||||
if (pos + 1 >= data.length())
|
||||
break;
|
||||
if (data.charAt(pos) == '"' && data.charAt(pos + 1) == '"') {
|
||||
pos += 2;
|
||||
return sb.toString();
|
||||
}
|
||||
} else if (c == '\n') {
|
||||
line++;
|
||||
sb.append(c);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
throw new SettingException("Invalid multiline basic String at line " + line + ": it never ends");
|
||||
}
|
||||
|
||||
private char unescape(final char c) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
return '\b';
|
||||
case 't':
|
||||
return '\t';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 'f':
|
||||
return '\f';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case '"':
|
||||
return '"';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'u': {// unicode uXXXX
|
||||
if (data.length() - pos < 5)
|
||||
throw new SettingException("Invalid unicode code point at line " + line);
|
||||
final String unicode = data.substring(pos, pos + 4);
|
||||
pos += 4;
|
||||
try {
|
||||
final int hexVal = Integer.parseInt(unicode, 16);
|
||||
return (char) hexVal;
|
||||
} catch (final NumberFormatException ex) {
|
||||
throw new SettingException("Invalid unicode code point at line " + line, ex);
|
||||
}
|
||||
}
|
||||
case 'U': {// unicode UXXXXXXXX
|
||||
if (data.length() - pos < 9)
|
||||
throw new SettingException("Invalid unicode code point at line " + line);
|
||||
final String unicode = data.substring(pos, pos + 8);
|
||||
pos += 8;
|
||||
try {
|
||||
final int hexVal = Integer.parseInt(unicode, 16);
|
||||
return (char) hexVal;
|
||||
} catch (final NumberFormatException ex) {
|
||||
throw new SettingException("Invalid unicode code point at line " + line, ex);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new SettingException("Invalid escape sequence: \"\\" + c + "\" at line " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a char to a String. The char is escaped if needed.
|
||||
*/
|
||||
private String toString(final char c) {
|
||||
switch (c) {
|
||||
case '\b':
|
||||
return "\\b";
|
||||
case '\t':
|
||||
return "\\t";
|
||||
case '\n':
|
||||
return "\\n";
|
||||
case '\r':
|
||||
return "\\r";
|
||||
case '\f':
|
||||
return "\\f";
|
||||
default:
|
||||
return String.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package cn.hutool.setting.toml;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.setting.SettingException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class for writing TOML v0.4.0.
|
||||
* <h1>DateTimes support</h1>
|
||||
* <p>
|
||||
* Any {@link TemporalAccessor} may be added in a Map passed to this writer, this writer can only write three
|
||||
* kind of datetimes: {@link LocalDate}, {@link LocalDateTime} and {@link ZonedDateTime}.
|
||||
* </p>
|
||||
* <h1>Lenient bare keys</h1>
|
||||
* <p>
|
||||
* The {@link TomlWriter} always outputs data that strictly follows the TOML specification. Any key that
|
||||
* contains one
|
||||
* or more non-strictly valid character is surrounded by quotes.
|
||||
* </p>
|
||||
*
|
||||
* @author TheElectronWill
|
||||
*/
|
||||
public class TomlWriter {
|
||||
|
||||
private final Writer writer;
|
||||
private final int indentSize;
|
||||
private final char indentCharacter;
|
||||
private final String lineSeparator;
|
||||
private final LinkedList<String> tablesNames = new LinkedList<>();
|
||||
private int lineBreaks = 0, indentationLevel = -1;// -1 to prevent indenting the first level
|
||||
|
||||
/**
|
||||
* Creates a new TomlWriter with the defaults parameters. The system line separator is used (ie '\n' on
|
||||
* Linux and OSX, "\r\n" on Windows). This is exactly the same as
|
||||
* {@code TomlWriter(writer, 1, false, System.lineSeparator()}.
|
||||
*
|
||||
* @param writer where to write the data
|
||||
*/
|
||||
public TomlWriter(final Writer writer) {
|
||||
this(writer, 1, false, System.lineSeparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new TomlWriter with the specified parameters. The system line separator is used (ie '\n' on
|
||||
* Linux and OSX, "\r\n" on Windows). This is exactly the same as
|
||||
* {@code TomlWriter(writer, indentSize, indentWithSpaces, System.lineSeparator())}.
|
||||
*
|
||||
* @param writer where to write the data
|
||||
* @param indentSize the size of each indent
|
||||
* @param indentWithSpaces true to indent with spaces, false to indent with tabs
|
||||
*/
|
||||
public TomlWriter(final Writer writer, final int indentSize, final boolean indentWithSpaces) {
|
||||
this(writer, indentSize, indentWithSpaces, System.lineSeparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new TomlWriter with the specified parameters.
|
||||
*
|
||||
* @param writer where to write the data
|
||||
* @param indentSize the size of each indent
|
||||
* @param indentWithSpaces true to indent with spaces, false to indent with tabs
|
||||
* @param lineSeparator the String to write to break lines
|
||||
*/
|
||||
public TomlWriter(final Writer writer, final int indentSize, final boolean indentWithSpaces, final String lineSeparator) {
|
||||
this.writer = writer;
|
||||
this.indentSize = indentSize;
|
||||
this.indentCharacter = indentWithSpaces ? CharUtil.SPACE : CharUtil.TAB;
|
||||
this.lineSeparator = lineSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying writer, flushing it first.
|
||||
*
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the underlying writer.
|
||||
*
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
public void flush() throws IOException {
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified data in the TOML format.
|
||||
*
|
||||
* @param data the data to write
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
public void write(final Map<String, Object> data) throws IOException {
|
||||
writeTableContent(data);
|
||||
}
|
||||
|
||||
private void writeTableName() throws IOException {
|
||||
final Iterator<String> it = tablesNames.iterator();
|
||||
while (it.hasNext()) {
|
||||
final String namePart = it.next();
|
||||
writeKey(namePart);
|
||||
if (it.hasNext()) {
|
||||
write('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTableContent(final Map<String, Object> table) throws IOException {
|
||||
writeTableContent(table, true);
|
||||
writeTableContent(table, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the content of a table.
|
||||
*
|
||||
* @param table the table to write
|
||||
* @param simpleValues true to write only the simple values (and the normal arrays), false to write only
|
||||
* the tables
|
||||
* (and the arrays of tables).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writeTableContent(final Map<String, Object> table, final boolean simpleValues) throws IOException {
|
||||
for (final Map.Entry<String, Object> entry : table.entrySet()) {
|
||||
final String name = entry.getKey();
|
||||
final Object value = entry.getValue();
|
||||
if (value instanceof Collection) {// array
|
||||
final Collection<?> c = (Collection<?>) value;
|
||||
if (false == c.isEmpty() && c.iterator().next() instanceof Map) {// array of tables
|
||||
if (simpleValues) {
|
||||
continue;
|
||||
}
|
||||
tablesNames.addLast(name);
|
||||
indentationLevel++;
|
||||
for (final Object element : c) {
|
||||
indent();
|
||||
write("[[");
|
||||
writeTableName();
|
||||
write("]]\n");
|
||||
final Map<String, Object> map = (Map<String, Object>) element;
|
||||
writeTableContent(map);
|
||||
}
|
||||
indentationLevel--;
|
||||
tablesNames.removeLast();
|
||||
} else {// normal array
|
||||
if (false == simpleValues) {
|
||||
continue;
|
||||
}
|
||||
indent();
|
||||
writeKey(name);
|
||||
write(" = ");
|
||||
writeArray(c);
|
||||
}
|
||||
} else if (value instanceof Object[]) {// array
|
||||
final Object[] array = (Object[]) value;
|
||||
if (array.length > 0 && array[0] instanceof Map) {// array of tables
|
||||
if (simpleValues) {
|
||||
continue;
|
||||
}
|
||||
tablesNames.addLast(name);
|
||||
indentationLevel++;
|
||||
for (final Object element : array) {
|
||||
indent();
|
||||
write("[[");
|
||||
writeTableName();
|
||||
write("]]\n");
|
||||
final Map<String, Object> map = (Map<String, Object>) element;
|
||||
writeTableContent(map);
|
||||
}
|
||||
indentationLevel--;
|
||||
tablesNames.removeLast();
|
||||
} else {// normal array
|
||||
if (false == simpleValues) {
|
||||
continue;
|
||||
}
|
||||
indent();
|
||||
writeKey(name);
|
||||
write(" = ");
|
||||
writeString(ArrayUtil.toString(array));
|
||||
}
|
||||
} else if (value instanceof Map) {// table
|
||||
if (simpleValues) {
|
||||
continue;
|
||||
}
|
||||
tablesNames.addLast(name);
|
||||
indentationLevel++;
|
||||
|
||||
indent();
|
||||
write('[');
|
||||
writeTableName();
|
||||
write(']');
|
||||
newLine();
|
||||
writeTableContent((Map<String, Object>) value);
|
||||
|
||||
indentationLevel--;
|
||||
tablesNames.removeLast();
|
||||
} else {// simple value
|
||||
if (!simpleValues) {
|
||||
continue;
|
||||
}
|
||||
indent();
|
||||
writeKey(name);
|
||||
write(" = ");
|
||||
writeValue(value);
|
||||
}
|
||||
newLine();
|
||||
}
|
||||
newLine();
|
||||
}
|
||||
|
||||
private void writeKey(final String key) throws IOException {
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
final char c = key.charAt(i);
|
||||
if (false == isValidCharOfKey(c)) {
|
||||
// 含有非法字符,包装之
|
||||
writeString(key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
write(key);
|
||||
}
|
||||
|
||||
private static boolean isValidCharOfKey(final char c) {
|
||||
return (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '-' ||
|
||||
c == '_';
|
||||
}
|
||||
|
||||
private void writeString(final String str) throws IOException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append('"');
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
final char c = str.charAt(i);
|
||||
addEscaped(c, sb);
|
||||
}
|
||||
sb.append('"');
|
||||
write(sb.toString());
|
||||
}
|
||||
|
||||
private void writeArray(final Collection<?> c) throws IOException {
|
||||
write('[');
|
||||
for (final Object element : c) {
|
||||
writeValue(element);
|
||||
write(", ");
|
||||
}
|
||||
write(']');
|
||||
}
|
||||
|
||||
private void writeValue(final Object value) throws IOException {
|
||||
if (value instanceof String) {
|
||||
writeString((String) value);
|
||||
} else if (value instanceof Number || value instanceof Boolean) {
|
||||
write(value.toString());
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
String formatted = Toml.DATE_FORMATTER.format((TemporalAccessor) value);
|
||||
if (formatted.endsWith("T"))// If the last character is a 'T'
|
||||
{
|
||||
formatted = formatted.substring(0, formatted.length() - 1);// removes it because it's invalid.
|
||||
}
|
||||
write(formatted);
|
||||
} else if (value instanceof Collection) {
|
||||
writeArray((Collection<?>) value);
|
||||
} else if (ArrayUtil.isArray(value)) {
|
||||
write(ArrayUtil.toString(value));
|
||||
} else if (value instanceof Map) {// should not happen because an array of tables is detected by
|
||||
// writeTableContent()
|
||||
throw new IOException("Unexpected value " + value);
|
||||
} else {
|
||||
throw new SettingException("Unsupported value of type " + value.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
private void newLine() throws IOException {
|
||||
if (lineBreaks <= 1) {
|
||||
writer.write(lineSeparator);
|
||||
lineBreaks++;
|
||||
}
|
||||
}
|
||||
|
||||
private void write(final char c) throws IOException {
|
||||
writer.write(c);
|
||||
lineBreaks = 0;
|
||||
}
|
||||
|
||||
private void write(final String str) throws IOException {
|
||||
writer.write(str);
|
||||
lineBreaks = 0;
|
||||
}
|
||||
|
||||
private void indent() throws IOException {
|
||||
for (int i = 0; i < indentationLevel; i++) {
|
||||
for (int j = 0; j < indentSize; j++) {
|
||||
write(indentCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addEscaped(final char c, final StringBuilder sb) {
|
||||
switch (c) {
|
||||
case '\b':
|
||||
sb.append("\\b");
|
||||
break;
|
||||
case '\t':
|
||||
sb.append("\\t");
|
||||
break;
|
||||
case '\n':
|
||||
sb.append("\\n");
|
||||
break;
|
||||
case '\\':
|
||||
sb.append("\\\\");
|
||||
break;
|
||||
case '\r':
|
||||
sb.append("\\r");
|
||||
break;
|
||||
case '\f':
|
||||
sb.append("\\f");
|
||||
break;
|
||||
case '"':
|
||||
sb.append("\\\"");
|
||||
break;
|
||||
default:
|
||||
sb.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TOML(Tom's Obvious, Minimal Language)配置文件解析和生成
|
||||
*
|
||||
* <p>
|
||||
* 规范:https://toml.io/cn/
|
||||
* </p>
|
||||
* <p>
|
||||
* 参考实现:https://github.com/TheElectronWill/TOML-javalib
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.setting.toml;
|
33
hutool-setting/src/test/resources/test.toml
Normal file
33
hutool-setting/src/test/resources/test.toml
Normal file
@ -0,0 +1,33 @@
|
||||
# This is a TOML document.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
dob = 1979-05-27T07:32:00-08:00 # First class dates
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8000, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# Indentation (tabs and/or spaces) is allowed but not required
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ]
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user