TOML v1.0.0-rc.3
Tom 的显而易见、最简语言。
由 Tom Preston-Werner、Pradyun Gedam 等人编写。
目标
TOML 旨在成为一种最简的配置文件格式,由于语义明显,因此易于阅读。TOML 旨在无歧义地映射到哈希表。TOML 应该易于解析为各种语言中的数据结构。
规范
- TOML 区分大小写。
- TOML 文件必须是有效的 UTF-8 编码的 Unicode 文档。
- 空白字符表示制表符 (0x09) 或空格 (0x20)。
- 换行符表示 LF (0x0A) 或 CRLF (0x0D 0x0A)。
注释
哈希符号将该行其余部分标记为注释,除非在字符串内部。
# This is a full-line comment
key = "value" # This is a comment at the end of a line
another = "# This is not a comment"
注释中不允许使用除制表符之外的控制字符 (U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。
键值对
TOML 文档的基本构建块是键值对。
键位于等号左侧,值位于右侧。键名和值周围的空格将被忽略。键、等号和值必须位于同一行(尽管某些值可以跨多行)。
key = "value"
值必须具有以下类型之一。
未指定的数值无效。
key = # INVALID
键值对之后必须有一个换行符(或 EOF)。(有关例外情况,请参阅内联表。)
first = "Tom" last = "Preston-Werner" # INVALID
键
键可以是裸键、带引号的键或点分键。
**裸键**只能包含 ASCII 字母、ASCII 数字、下划线和连字符 (A-Za-z0-9_-
)。请注意,裸键允许仅由 ASCII 数字组成,例如 1234
,但始终被解释为字符串。
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"
**带引号的键**遵循与基本字符串或字面字符串完全相同的规则,并允许您使用更广泛的键名集。最佳实践是使用裸键,除非绝对必要。
"127.0.0.1" = "value"
"character encoding" = "value"
"ʎǝʞ" = "value"
'key2' = "value"
'quoted "value"' = "value"
裸键必须非空,但允许使用空带引号的键(尽管不建议这样做)。
= "no key name" # INVALID
"" = "blank" # VALID but discouraged
'' = 'blank' # VALID but discouraged
**点分键**是由用点连接的一系列裸键或带引号的键组成的序列。这允许将类似的属性组合在一起
name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true
在 JSON 中,将得到以下结构
{
"name": "Orange",
"physical": {
"color": "orange",
"shape": "round"
},
"site": {
"google.com": true
}
}
点分部分周围的空格将被忽略,但是,最佳实践是不使用任何多余的空格。
多次定义同一个键是无效的。
# DO NOT DO THIS
name = "Tom"
name = "Pradyun"
请注意,裸键和带引号的键是等价的
# THIS WILL NOT WORK
spelling = "favorite"
"spelling" = "favourite"
只要尚未直接定义某个键,您仍然可以写入它以及其中的名称。
# This makes the key "fruit" into a table.
fruit.apple.smooth = true
# So then you can add to the table "fruit" like so:
fruit.orange = 2
# THE FOLLOWING IS INVALID
# This defines the value of fruit.apple to be an integer.
fruit.apple = 1
# But then this treats fruit.apple like it's a table.
# You can't turn an integer into a table.
fruit.apple.smooth = true
不建议以乱序定义点分键。
# VALID BUT DISCOURAGED
apple.type = "fruit"
orange.type = "fruit"
apple.skin = "thin"
orange.skin = "thick"
apple.color = "red"
orange.color = "orange"
# RECOMMENDED
apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"
orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"
由于裸键允许仅由 ASCII 整数组成,因此可以编写看起来像浮点数但实际上是两部分点分键的点分键。除非有充分的理由(您可能没有),否则请勿这样做。
3.14159 = "pi"
上面提到的 TOML 映射到以下 JSON。
{ "3": { "14159": "pi" } }
字符串
有四种方法可以表达字符串:基本字符串、多行基本字符串、字面字符串和多行字面字符串。所有字符串都必须仅包含有效的 UTF-8 字符。
**基本字符串**用引号 ("
) 括起来。可以使用任何 Unicode 字符,但以下字符除外:引号、反斜杠和除制表符之外的控制字符 (U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。
str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
为方便起见,一些常用字符具有紧凑的转义序列。
\b - backspace (U+0008)
\t - tab (U+0009)
\n - linefeed (U+000A)
\f - form feed (U+000C)
\r - carriage return (U+000D)
\" - quote (U+0022)
\\ - backslash (U+005C)
\uXXXX - unicode (U+XXXX)
\UXXXXXXXX - unicode (U+XXXXXXXX)
可以使用 \uXXXX
或 \UXXXXXXXX
形式转义任何 Unicode 字符。转义代码必须是有效的 Unicode 标量值。
上面未列出的所有其他转义序列均为保留序列;如果使用它们,TOML 应产生错误。
有时您需要表达文本段落(例如翻译文件)或希望将非常长的字符串分成多行。TOML 使此操作变得简单。
**多行基本字符串**两侧用三个引号括起来,并允许换行。紧跟在起始分隔符后的换行符将被修剪。所有其他空格和换行符将保持不变。
str1 = """
Roses are red
Violets are blue"""
TOML 解析器可以随意将换行符规范化为其平台上合适的任何内容。
# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"
# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"
要编写不引入多余空格的长字符串,请使用“行尾反斜杠”。当一行上的最后一个非空格字符是未转义的 \
时,它将与所有空格(包括换行符)一起被修剪,直到下一个非空格字符或结束分隔符为止。所有对基本字符串有效的转义序列也对多行基本字符串有效。
# The following strings are byte-for-byte equivalent:
str1 = "The quick brown fox jumps over the lazy dog."
str2 = """
The quick brown \
fox jumps over \
the lazy dog."""
str3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
可以使用任何 Unicode 字符,但以下字符除外:反斜杠和除制表符、换行符和回车符之外的控制字符 (U+0000 至 U+0008、U+000B、U+000C、U+000E 至 U+001F、U+007F)。
您可以在多行基本字符串中的任何位置编写一个引号或两个相邻的引号。它们也可以写在分隔符的正内侧。
str4 = """Here are two quotation marks: "". Simple enough."""
# str5 = """Here are three quotation marks: """.""" # INVALID
str5 = """Here are three quotation marks: ""\"."""
str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""
# "This," she said, "is just a pointless statement."
str7 = """"This," she said, "is just a pointless statement.""""
如果您经常指定 Windows 路径或正则表达式,那么必须转义反斜杠很快就会变得乏味且容易出错。为了提供帮助,TOML 支持根本不允许转义的字面字符串。
**字面字符串**用单引号括起来。与基本字符串一样,它们必须出现在一行上
# 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*>'
由于没有转义,因此无法在用单引号括起来的字面字符串内编写单引号。幸运的是,TOML 支持字面字符串的多行版本,它解决了此问题。
**多行字面字符串**两侧用三个单引号括起来,并允许换行。与字面字符串一样,没有任何转义。紧跟在起始分隔符后的换行符将被修剪。分隔符之间的所有其他内容将按原样解释,无需修改。
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
您可以在多行字面字符串中的任何位置编写 1 个或 2 个单引号,但不允许使用三个或更多个单引号的序列。
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''
# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID
apos15 = "Here are fifteen apostrophes: '''''''''''''''"
# 'That,' she said, 'is still pointless.'
str = ''''That,' she said, 'is still pointless.''''
字面字符串中不允许使用除制表符之外的控制字符。因此,对于二进制数据,建议您使用 Base64 或其他合适的 ASCII 或 UTF-8 编码。该编码的处理方式将是特定于应用程序的。
整数
整数是整数。正数可以以加号为前缀。负数以减号为前缀。
int1 = +99
int2 = 42
int3 = 0
int4 = -17
对于大数,您可以在数字之间使用下划线来增强可读性。每个下划线必须两侧至少有一个数字。
int5 = 1_000
int6 = 5_349_221
int7 = 53_49_221 # Indian number system grouping
int8 = 1_2_3_4_5 # VALID but discouraged
不允许前导零。整数 -0
和 +0
有效,并且与无前缀的零相同。
非负整数也可以用十六进制、八进制或二进制表示。在这些格式中,不允许前导 +
,并且允许前导零(在前缀之后)。十六进制值不区分大小写。数字之间允许使用下划线(但在前缀和值之间不允许)。
# 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 位有符号整数(从 −2^63 到 2^63−1)。如果无法无损表示整数,则必须抛出错误。
浮点数
浮点数应实现为 IEEE 754 binary64 值。
浮点数由整数部分(遵循与十进制整数相同的规则)后跟小数部分和/或指数部分组成。如果同时存在小数部分和指数部分,则小数部分必须位于指数部分之前。
# fractional
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01
# exponent
flt4 = 5e+22
flt5 = 1e06
flt6 = -2E-2
# both
flt7 = 6.626e-34
小数部分是小数点后跟一个或多个数字。
指数部分是 E(大写或小写)后跟整数部分(遵循与十进制整数相同的规则,但可以包含前导零)。
如果使用小数点,则必须两侧至少有一个数字。
# INVALID FLOATS
invalid_float_1 = .7
invalid_float_2 = 7.
invalid_float_3 = 3.e+20
与整数类似,您可以使用下划线来增强可读性。每个下划线必须两侧至少有一个数字。
flt8 = 224_617.445_991_228
浮点值 -0.0
和 +0.0
有效,并且应根据 IEEE 754 进行映射。
特殊浮点值也可以表达。它们始终为小写。
# 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
布尔值
布尔值只是您习惯使用的标记。始终为小写。
bool1 = true
bool2 = false
带偏移量的日期时间
要明确表示特定时间点,您可以使用 RFC 3339 格式的带偏移量的日期时间。
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 节所允许)。
odt4 = 1979-05-27 07:32:00Z
需要毫秒精度。小数秒的进一步精度是特定于实现的。如果值包含超出实现支持范围的精度,则必须截断而不是四舍五入附加精度。
本地日期时间
如果您从 RFC 3339 格式的日期时间中省略偏移量,它将表示给定的日期时间,与偏移量或时区无关。在没有其他信息的情况下,无法将其转换为时间点。如果需要转换为时间点,则特定于实现。
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999
需要毫秒精度。小数秒的进一步精度是特定于实现的。如果值包含超出实现支持范围的精度,则必须截断而不是四舍五入附加精度。
本地日期
如果您仅包含 RFC 3339 格式的日期时间中的日期部分,它将表示整个日期,与偏移量或时区无关。
ld1 = 1979-05-27
本地时间
如果您仅包含 RFC 3339 格式的日期时间中的时间部分,它将表示一天中的时间,与特定日期或任何偏移量或时区无关。
lt1 = 07:32:00
lt2 = 00:32:00.999999
需要毫秒精度。小数秒的进一步精度是特定于实现的。如果值包含超出实现支持范围的精度,则必须截断而不是四舍五入附加精度。
数组
数组是用方括号括起来的包含值的数组。空格将被忽略。元素用逗号分隔。数组可以包含与键值对中允许的相同数据类型的值。可以混合不同类型的值。
integers = [ 1, 2, 3 ]
colors = [ "red", "yellow", "green" ]
nested_array_of_int = [ [ 1, 2 ], [3, 4, 5] ]
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
string_array = [ "all", 'strings', """are the same""", '''type''' ]
# Mixed-type arrays are allowed
numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]
contributors = [
"Foo Bar <[email protected]>",
{ name = "Baz Qux", email = "[email protected]", url = "https://example.com/bazqux" }
]
数组可以跨多行。在数组的最后一个值之后允许使用终止逗号(也称为尾随逗号)。任何数量的换行符和注释都可以在值、逗号和结束括号之前。
integers2 = [
1, 2, 3
]
integers3 = [
1,
2, # this is ok
]
表
表(也称为哈希表或字典)是键值对的集合。它们单独出现在一行上的方括号中。您可以将它们与数组区分开来,因为数组永远只是值。
[table]
在其下,直到下一个表或 EOF 都是该表的键值。表中的键值对不保证按任何特定顺序排列。
[table-1]
key1 = "some string"
key2 = 123
[table-2]
key1 = "another string"
key2 = 456
表的命名规则与键相同(请参阅上面键的定义)。
[dog."tater.man"]
type.name = "pug"
在 JSON 中,将得到以下结构
{ "dog": { "tater.man": { "type": { "name": "pug" } } } }
键周围的空格将被忽略,但是,最佳实践是不使用任何多余的空格。
[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']
如果您不想指定所有超级表,则不必这样做。TOML 知道如何为您完成。
# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work
[x] # defining a super-table afterward is ok
允许使用空表,并且它们只是在其中没有键值对。
与键类似,您不能多次定义同一个表。这样做是无效的。
# DO NOT DO THIS
[fruit]
apple = "red"
[fruit]
orange = "orange"
# DO NOT DO THIS EITHER
[fruit]
apple = "red"
[fruit.apple]
texture = "smooth"
不建议以乱序定义表。
# VALID BUT DISCOURAGED
[fruit.apple]
[animal]
[fruit.orange]
# RECOMMENDED
[fruit.apple]
[fruit.orange]
[animal]
点分键将每个点左侧的所有内容定义为表。由于表不能多次定义,因此不允许使用 [table]
标题重新定义此类表。同样,不允许使用点分键重新定义已在 [table]
形式中定义的表。
但是,[table]
形式可用于定义通过点分键定义的表中的子表。
[fruit]
apple.color = "red"
apple.taste.sweet = true
# [fruit.apple] # INVALID
# [fruit.apple.taste] # INVALID
[fruit.apple.texture] # you can add sub-tables
smooth = true
内联表
内联表格提供了一种更紧凑的语法来表达表格。它们尤其适用于分组数据,否则这些数据可能会很快变得冗长。内联表格用花括号括起来:{
和 }
。在花括号内,可以出现零个或多个以逗号分隔的键/值对。键/值对采用与标准表格中键/值对相同的形式。所有值类型都允许,包括内联表格。
内联表格旨在出现在一行上。在内联表格的最后一个键/值对之后不允许使用终止逗号(也称为尾随逗号)。在花括号之间不允许出现换行符,除非它们在值中有效。即使如此,也强烈建议不要将内联表格拆分成多行。如果您发现自己渴望这样做,这意味着您应该使用标准表格。
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }
以上内联表格与以下标准表格定义相同
[name]
first = "Tom"
last = "Preston-Werner"
[point]
x = 1
y = 2
[animal]
type.name = "pug"
内联表格完全定义了它们内部的键和子表格。不能向它们添加新的键和子表格。
[product]
type = { name = "Nail" }
# type.edible = false # INVALID
类似地,内联表格不能用于向已定义的表格添加键或子表格。
[product]
type.name = "Nail"
# type = { edible = false } # INVALID
表数组
尚未描述的最后一个语法允许编写表格数组。这可以通过使用双括号中的表名来表达。在该表名下,直到下一个表或文件结尾,都是该表的键/值。每个具有相同双括号名称的表都将是表数组中的一个元素。表格按照遇到的顺序插入。没有键/值对的双括号表将被视为空表。
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
在 JSON 中,这将为您提供以下结构。
{
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}
您也可以创建嵌套的表格数组。只需在子表格上使用相同的双括号语法即可。在嵌套的表格数组中,每个双括号子表格都将属于最近定义的表格元素。普通子表格(不是数组)同样属于最近定义的表格元素。
[[fruit]]
name = "apple"
[fruit.physical] # subtable
color = "red"
shape = "round"
[[fruit.variety]] # nested array of tables
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"
上面提到的 TOML 映射到以下 JSON。
{
"fruit": [
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"variety": [
{ "name": "red delicious" },
{ "name": "granny smith" }
]
},
{
"name": "banana",
"variety": [
{ "name": "plantain" }
]
}
]
}
如果表格或表格数组的父级是数组元素,则必须在定义子级之前已定义该元素。尝试反转该顺序必须在解析时产生错误。
# INVALID TOML DOC
[fruit.physical] # subtable, but to which parent element should it belong?
color = "red"
shape = "round"
[[fruit]] # parser must throw an error upon discovering that "fruit" is
# an array rather than a table
name = "apple"
尝试追加到静态定义的数组,即使该数组为空,也必须在解析时产生错误。
# INVALID TOML DOC
fruit = []
[[fruit]] # Not allowed
尝试使用与已建立数组相同的名称定义普通表格必须在解析时产生错误。尝试将普通表格重新定义为数组也必须产生解析时错误。
# INVALID TOML DOC
[[fruit]]
name = "apple"
[[fruit.variety]]
name = "red delicious"
# INVALID: This table conflicts with the previous array of tables
[fruit.variety]
name = "granny smith"
[fruit.physical]
color = "red"
shape = "round"
# INVALID: This array of tables conflicts with the previous table
[[fruit.physical]]
color = "green"
您也可以在适当的地方使用内联表格
points = [ { x = 1, y = 2, z = 3 },
{ x = 7, y = 8, z = 9 },
{ x = 2, y = 4, z = 8 } ]
文件名扩展名
TOML 文件应使用扩展名 .toml
。
MIME 类型
通过互联网传输 TOML 文件时,适当的 MIME 类型为 application/toml
。
ABNF 语法
TOML 语法的正式描述以单独的 ABNF 文件 形式提供。