TOML v1.0.0-rc.2
Tom's Obvious, Minimal Language.(汤姆的显而易见、极简语言)
作者: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 二进制 64 值。
浮点数由整数部分(遵循与十进制整数相同的规则)后跟小数部分和/或指数部分组成。如果同时存在小数部分和指数部分,则小数部分必须位于指数部分之前。
# 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
表数组
尚未描述的最后一个语法允许编写表格数组。这些可以通过使用双括号中的表名来表示。在该表名下,以及直到下一个表或文件结尾 (EOF) 之间,都是该表的键/值。每个具有相同双括号名称的表都将是表数组中的一个元素。表按遇到的顺序插入。没有键/值对的双括号表将被视为空表。
[[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
。