TOML v0.5.0

全称: Tom的(语义)明显、(配置)最小化的语言。 (Tom's Obvious, Minimal Language. By Tom Preston-Werner.)

注: 此规范仍然会发生很多变化。在未标记为1.0之前,你应该假设它是不稳定的,请酌情采用。

目标

TOML的目标是成为一个有明显语义而容易去阅读的最小化配置文件格式。 TOML被设计成可以无歧义地被映射为哈希表,从而很容易的被解析成各种语言中的数据结构。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 这是TOML文档示例.

title = "TOML Example"

[owner]
name = "Lance Uppercut"
dob = 1979-05-27T07:32:00-08:00 # 时间日期当然是一等公民了。

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

[servers]

# 你可以按你的意愿缩进。TOML并不关心你是用Tab还是空格。
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"

[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"

[clients]
data = [ ["gamma", "delta"], [1, 2] ]

# 数组里可以换行
hosts = [
"alpha",
"omega"
]

规格

  • TOML是大小写敏感的。
  • TOML文件必须只包含UTF-8编码的Unicode字符。
  • 空格是指制表符(0x09) 或空格 (0x20)。
  • 换行符是指LF(0x0A)或CRLF (0x0D0A).

注释

用符号#来表示注释:

1
2
# I am a comment. Hear me roar. Roar.
key = "value" # Yeah, you can do this.

键值对

一个 TOML 文档的主要构建块是键值对

= 的左边,在右边。周围的空白符忽略不计。=必须位于同一行(虽然一些可以被折成多行)。

1
key = "value"

可以是以下类型:字符串整数浮点数布尔值时间数组内联表。未确定的值是非法的。

1
key = # 非法

可以是裸露的裸键,也可以是被包裹在一对 " 的内部的引用键裸键只能存在字母、数字、下划线和破折号(a-zA-Z0-9_-)。注意,裸键可以只由数字组成,例如 1234,但它总是被解析为字符串。

1
2
3
4
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"

引用键遵循与基本字符串字面字符串完全相同的规范,并允许使用更广泛的键名称。最好的做法是使用裸键,除非必要情况。

1
2
3
4
5
"127.0.0.1" = "value"
"character encoding" = "value"
"ʎǝʞ" = "value"
'key2' = "value"
'quoted "value"' = "value"

裸键不允许为空,但引用键允许(虽然这会导致不可见)。

1
2
3
= "no key name"  # 非法
"" = "blank" # 合法但不可见
'' = 'blank' # 合法但不可见

虚线键是一系列用点连接的裸键或引号键。 这允许将类似的属性组合在一起:

1
2
3
4
name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true

在JSON上结构:

1
2
3
4
5
6
7
8
9
10
{
"name": "Orange",
"physical": {
"color": "orange",
"shape": "round"
},
"site": {
"google.com": true
}
}

忽略点分隔部分周围的空白,但最佳做法是不使用任何无关的空白。

多次定义键无效。

1
2
3
# DO NOT DO THIS
name = "Tom"
name = "Pradyun"

由于允许裸键仅由ASCII整数组成,因此可以编写看起来像浮点数但是由2部分虚线键组成的虚线键。 除非你有充分的理由(你可能不这样做),否则不要这样做。

1
3.14159 = "pi"

在JSON上结构:

1
{ "3": { "14159": "pi" } }

只要没有直接定义键,您仍然可以写入键及其中的名称。

1
2
a.b.c = 1
a.d = 2
1
2
3
# THIS IS INVALID
a.b = 1
a.b.c = 2

字符串

有四种方法来表示字符串:基本字符串、多行基本字符串、字面量和多行字面量。所有的字符串必须只包含有效的UTF-8字符。

基本字符串 是由引号括起来的任意字符串,除了那些必须要转义的,比如:双引号、反斜杠和控制字符(U+0000到U+001F)。

1
str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."

常用的转义序列:

1
2
3
4
5
6
7
8
9
\b         - 退格键   (U+0008)
\t - 制表符 (U+0009)
\n - 换行符 (U+000A)
\f - 换页符 (U+000C)
\r - 回车符 (U+000D)
\" - " (U+0022)
\\ - \ (U+005C)
\uXXXX - Unicode (U+XXXX)
\UXXXXXXXX - Unicode (U+XXXXXXXX)

任意Unicode字符都可能被转义为\uXXXX\UXXXXXXXX的形式。这些转义代码必须是有效的Unicode标量值

所有未出现在上面名单中的转义序列必须保留,如果使用,TOML应该会产生错误。

有时你需要表达一段文本(比如,翻译文件),或者是将很长的字符串分成多行。TOML很容易处理这种情况。 多行基本字符串 是被三引号括起来的字符串,并且允许换行。紧跟起始界定符后面的换行符会被剪掉,而其他的所有空格和换行字符仍然被保留。

1
2
3
key1 = """
Roses are red
Violets are blue"""

TOML解析器应该能正常处理不同平台下的换行符。

1
2
3
4
5
# 对于Unix系统,上面的多行字符串应该是这样的:
key2 = "Roses are red\nViolets are blue"

# 对于Windows系统,最可能等价于这样的:
key3 = "Roses are red\r\nViolets are blue"

在行尾使用\,可以避免在写长字符串的时候引入多余的空格。 \将会删除当前位置到下个非空字符或结束界定符之间的所有空格(包括换行符)。 如果在起始界定符之后的第一个字符是反斜杠和一个换行符,那么从此位置到下个非空白字符或结束界定符之间的所有空格和换行符都会被剪掉。 所有的转义序列对基本字符串都有效,也对多行基本字符串有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 以下每个字符串都是相同的
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.\
"""

任何Unicode字符都可能被用到,除了那些可能需要转义的字符:反斜杠和控制字符(U+0000 到 U+001F)。 引号不需要转义,除非它们的存在可能会造成提前关闭界定符。

如果你需要频繁的指定Windows的路径或正则表达式,那么不得不添加转义符就会变的繁琐和容易出错。 TOML支持完全不允许转义的字面量字符串来帮助你解决此类问题。 字面量字符串 是被单引号包含的字符串,跟基本字符串一样,它们一定是以单行出现:

1
2
3
4
5
# 所见即所得.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'

因为没有转义,所以在一个被单引号封闭的字面量字符串里面没有办法写单引号。 幸运的是,TOML支持多行版本的字面量字符串来解决这个问题。 多行字面量字符串 是被三个单引号括起来的字符串,并且允许换行。 跟字面量字符串一样,这也没有任何转义。 紧跟起始界定符的换行符会被剪掉。界定符之间的所有其他内容都会被按照原样解释而无需修改。

1
2
3
4
5
6
7
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''

对于二进制数据,建议你使用Base64或其他适合的编码,比如ASCII或UTF-8编码。具体的处理取决于特定的应用。

整数

整数就是没有小数点的数字。正数前面也可以用加号,负数需要用负号前缀表示。

1
2
3
4
int1 = +99
int2 = 42
int3 = 0
int4 = -17

对于大整数,你可以用下划线提高可读性。每个下划线两边至少包含一个数字。

1
2
3
int5 = 1_000
int6 = 5_349_221
int7 = 1_2_3_4_5 # 有效,但不建议这样写

前导零是不允许的。也不允许十六进制(Hex)、八进制(octal)和二进制形式。 诸如“无穷”和“非数字”这样的不能用一串数字表示的值都不被允许。

1
2
3
4
5
6
7
8
9
10
11
# hexadecimal with prefix `0x`
hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef

# octal with prefix `0o`
oct1 = 0o01234567
oct2 = 0o755 # useful for Unix file permissions

# binary with prefix `0b`
bin1 = 0b11010110

预期的范围是64位 (signed long)(−9,223,372,036,854,775,808 到 9,223,372,036,854,775,807).

浮点数

一个浮点数由整数部分(可能是带有加号或减号前缀的)和小数部分和(或)指数部分组成的数。 如果只有小数部分和指数部分,那么小数部分必须放在指数部分前面。

1
2
3
4
5
6
7
8
9
10
11
12
# 小数
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01

# 指数
flt4 = 5e+22
flt5 = 1e6
flt6 = -2E-2

# 小数和指数同时存在
flt7 = 6.626e-34

小数部分是指在小数点后面的一个或多个数字。

指数部分是指E(大写或小写)后面的整数部分(可能用加号或减号为前缀)

和整数类似,你可以用下划线来提高可读性。每个下划线两边至少包含一个数字。

1
flt8 = 224_617.445_991_228

预期精度为64位 (double)。

1
2
3
4
5
6
7
8
9
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity

# not a number
sf4 = nan # actual sNaN/qNaN encoding is implementation specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation specific

布尔值

布尔值是小写的true和false。

1
2
bool1 = true
bool2 = false

偏移日期时间

为了明确地表示特定的日期时间,可以使用 RFC 3339 来格式化日期时间的偏移量。

1
2
3
odt1 = 1979-05-27T07:32:00Z
odt2 = 1979-05-27T00:32:00-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00

为了便于阅读,您可以用空格替换日期和时间之间的T分隔符(RFC 3339第5.6节允许)。

1
odt4 = 1979-05-27 07:32:00Z

秒的小数部分的精度至少是毫秒级。如果值的精度远比实现的要高,那么多余的精度必须被截断,而不是舍入。

本地日期时间

如果省略 RFC 3339 格式的日期时间的偏移量,那么它将只表示给定的日期时间且与偏移量和时区没有任何关系。如果没有附加信息,那么它不能被转换成即时时间。如果需要转换为即时时间,则取决于具体的实现。

1
2
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999

秒的小数部分的精度至少是毫秒级。如果值的精度远比实现的要高,那么多余的精度必须被截断,而不是舍入。

本地日期

如果只包括 RFC 3339 格式的日期时间的日期部分,那么它将只表示给定的日期且与偏移量和时区没有任何关系。

1
ld1 = 1979-05-27

本地时间

如果只包括 RFC 3339 格式的日期时间的时间部分,那么它将只表示给定的时间且与偏移量和时区没有任何关系。

1
2
lt1 = 07:32:00
lt2 = 00:32:00.999999

秒的小数部分的精度至少是毫秒级。如果值的精度远比实现的要高,那么多余的精度必须被截断,而不是舍入。

数组

数组是由方括号包括的基本单元。空格会被忽略。 数组中的元素由逗号分隔。数据类型不能混用(所有的字符串均为同一类型)。

1
2
3
4
5
6
arr1 = [ 1, 2, 3 ]
arr2 = [ "red", "yellow", "green" ]
arr3 = [ [ 1, 2 ], [3, 4, 5] ]
arr4 = [ "all", 'strings', """are the same""", '''type''']
arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
arr6 = [ 1, 2.0 ] # INVALID

数组也可以多行。所以,除了忽略空格之外,数组也忽略了括号之间的换行符。 在结束括号之前存在逗号是可以的。

1
2
3
4
5
6
7
8
arr7 = [
1, 2, 3
]

arr8 = [
1,
2, # this is ok
]

表(也被称为哈希表或字典)是键值对集合。表格名由方括号包裹,自成一行。 注意和数组相区分,数组里只有值。

1
[table]

在表名之下,直到下一个表或文件尾(EOF)之间都是该表的键值对。 键是等号符左边的值,值是等号符右边的值。 键名和值周围的空格都将被忽略。 键、等号和值,一定要在同一行(有些值可以多行表示)

键可以是裸的或由引号包括的。裸键 可能仅包含字母、数字、下划线和破折号。 引号键 遵循基本字符串的规则,允许你使用更广泛的键名。 除非有绝对的必要,否则最好是用裸键。

表中的键值对是无序的。

1
2
3
4
5
6
7
[table-1]
key1 = "some string"
key2 = 123

[table-2]
key1 = "another string"
key2 = 456

点(.)严禁在裸键中使用,因为它被用来表示嵌套表! 命名规则为被点分隔的部分应该属于同一个键。(见上文)。

1
2
[dog."tater.man"]
type = "pug"

等价于如下JSON格式:

1
{ "dog": { "tater.man": { "type": "pug" } } }

被点分隔部分周围的空格都会被忽略,但是最好不要使用任何多余的空格。

1
2
3
4
[a.b.c]          # this is best practice
[ d.e.f ] # same as [d.e.f]
[ g . h . i ] # same as [g.h.i]
[ j . "ʞ" . l ] # same as [j."ʞ".l]

如果你不想,你可以完全不去指定父表(super-tables)。TOML知道该如何处理。

1
2
3
4
# [x] 你
# [x.y] 不
# [x.y.z] 需要这些
[x.y.z.w] # 去处理这种情况

空表是允许的,其中没有键值对。

只要父表没有被直接定义,而且没有定义特定的键,你可以继续写入。你不能多次定义键或表。这样做是无效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 不要这么做

[a]
b = 1

[a]
c = 2
# 也不要这样做

[a]
b = 1

[a.b]
c = 2

所有的表名和键一定不能为空。

内联表

内联表提供一种更紧凑的语法来表示表。它们可以把数据分组,避免这些数据很快变得冗长。 内联表是由大括号{}括起来的。在大括号内可以存在零个或多个逗号分隔的键值对。 内联表里的键值对跟标准表里的键值对形式是一样的。允许所有的值类型,包括内联表。

内联表一般以单行出现。不允许换行符出现在大括号之间,除非是包含在值中的有效字符。 即便如此,也强烈建议不要在把内联表分成多行。如果你有这种需求,那么你应该去用标准表。

1
2
3
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }

上面的内联表完全等同于下面的标准表定义:

1
2
3
4
5
6
7
8
9
10
[name]
first = "Tom"
last = "Preston-Werner"

[point]
x = 1
y = 2

[animal]
type.name = "pug"

表数组

最后要介绍的类型是表数组。表数组可以通过包括在双括号内表格名来表达。 使用相同双括号名的每个表都是数组中的元素。表的顺序跟书写顺序一致。 没有键值对的双括号表会被当作空表。

1
2
3
4
5
6
7
8
9
10
[[products]]
name = "Hammer"
sku = 738594937

[[products]]

[[products]]
name = "Nail"
sku = 284758393
color = "gray"

等价于如下JSON格式:

1
2
3
4
5
6
7
{
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}

你也能创建内嵌的表数组。只需要对子表使用相同的双括号语法就可以。每个双括号子表将属于其上面最近定义的那个表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[[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"

上面的TOML对应于下面的JSON格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"fruit": [
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"variety": [
{ "name": "red delicious" },
{ "name": "granny smith" }
]
},
{
"name": "banana",
"variety": [
{ "name": "plantain" }
]
}
]
}

试图追加到静态定义的数组中,即使该数组为空或兼容类型,也必须在解析时抛出错误。

1
2
3
# 非法的 TOML 文档
fruit = []
[[fruit]] # 不允许

试图用已经定义的数组的名称来定义的正常表,在解析的时候一定会抛出错误。

1
2
3
4
5
6
7
8
9
10
# INVALID TOML DOC
[[fruit]]
name = "apple"

[[fruit.variety]]
name = "red delicious"

# This table conflicts with the previous table
[fruit.variety]
name = "granny smith"

你也可以在适合的地方使用内联表。

1
2
3
points = [ { x = 1, y = 2, z = 3 },
{ x = 7, y = 8, z = 9 },
{ x = 2, y = 4, z = 8 } ]