Aofei Sheng

TOML 语法规范

TOML

全称:Tom’s Obvious, Minimal Language

作者:Tom Preston-Werner

最新版本:v0.4.0

注意,这个规范还持续变动中,因此你必须时刻都假定着它是一个不稳定的规范并对此做出一些防范措施直到它正式发布了 1.0 版。

目标

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

例子

# 这是一个 TOML 的文档。

title = "TOML 例子"

[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # 日期是一等公民

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

[servers]

	# 缩进(制表符和(或)空格)是允许的,但不是必需的
	[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)。

注释

# 将该行的其余部分标记为注释。

# 这是一个占据整行的注释
key = "value" # 这是一个在行末的注释

键值对

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

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

key = "value"

可以是裸露的裸键,也可以是被包裹在一对 " 的内部的引用键裸键只能存在字母、数字、下划线和破折号(a-zA-Z0-9_-)。注意,裸键可以只由数字组成,例如 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"  # 非法
"" = "blank"     # 合法但不可见
'' = 'blank'     # 合法但不可见

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

key = # 非法

字符串

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

基本字符串被包裹在一对 " 的内部。任何的 Unicode 字符都可以直接使用,除了那些必须被转义的字符:"\ 和控制字符(U+0000U+001F)。

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

为了方便起见,一些流行的字符都有简短的转义序列。

\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 形式转义。转义的编码必须是有效的标量值

所有上面未列出的其他的转义序列都将被保留不能使用,如果使用,则必须在解析时抛出一个错误。

有些时候可能需要表示文本的段落(例如:翻译文件)或想要将一个非常长的字符串折成多行。TOML 完成起来非常容易。多行基本字符串被包裹在有限行两侧的一对 """ 的内部。新行起始位置的换行符将会被忽略不计。所有其他的空白符和换行符都将被保留下来。

str1 = """
Roses are red
Violets are blue"""

TOML 解析器必须兼容不同平台的换行符。

# 在 Unix 系统上,上面的多行字符串必须和这个一样:
str2 = "Roses are red\nViolets are blue"

# 在 Windows 系统上,必须和这个一样:
str3 = "Roses are red\r\nViolets are blue"

书写长字符串时,使用行结束符来避免引入多余的空白符。当行末的字符是 \ 时,TOML 将会忽略所有的空白符(包括换行符)直到遇到非空白符或 """。所有对于基本字符串有效的转义序列对于多行基本字符串也是有效的。

# 以下字符串完全相同:
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+0000U+001F)。" 不需要转义,除非它的存在会导致多行字符串过早结束。

TOML 还支持不含任何转义的字面字符串字面字符串被包裹在一对 ' 的内部,像基本字符串一样,它们必须位于同一行。

# 所见即所得。
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.
'''

对于二进制数据,建议使用 Base64 或其它合适的 ASCIIUTF-8 编码。编码的处理将是特定于应用程序的。

整数

整数分为正数负数正数前的 + 不是必须的,但负数前必须存在 -

int1 = +99
int2 = 42
int3 = 0
int4 = -17

对于较大的数字,可以使用 _ 来增强其可读性。_ 必须被数字包围。

int5 = 1_000
int6 = 5_349_221
int7 = 1_2_3_4_5     # 合法但不可见

前导零、十六进制、八进制和二进制都是不允许的。像是“infinity”和“not a number”这种不能表示为一个连续的数字序列的值也是不被允许的。

整数是 64 位的长整型,范围是:−9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。

浮点数

浮点数整数部分(与整数相同的规范)和小数部分和(或)指数部分组成。如果小数部分指数部分同时存在,那么小数部分必须先于指数部分

# 小数
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01

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

# 二者
flt7 = 6.626e-34

小数部分由一个 . 开始,然后是一个或多个数字组成。

指数部分由一个 E(大小写均可)开始,然后是一个整数部分(和整数遵循相同的规范)组成。

整数类似,浮点数可以使用 _ 来增强其可读性。_ 必须被数字包围。

flt8 = 9_224_617.445_991_228_313

浮点数是 64 位的(双)精度数。

布尔值

布尔值的用法和在其他语言中的一样,且必须小写。

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

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

本地日期时间

如果省略 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

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

数组

数组被包裹在 [] 的内部。空白符忽略不计。元素由 , 分隔。不能混用数据类型(字符串的不同实现被认为是同一种数据类型,因此必须使用不同类型的数组)。

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 ] # 非法

数组还可以被折成多行。所以除了空白符,数组还忽略了 [] 的内部的换行符和注释。] 前允许出现 ,

arr7 = [
  1, 2, 3
]

arr8 = [
  1,
  2, # 这是允许的
]

表格

表格(也叫哈希表字典)是一个键值对的集合。它们被包裹在 [] 的内部且自成一行。表格数组是不同的,因为数组只有值。

[table]

在此之下,直到下一个表格EOF 之前,都是该表格键值对表格中的键值对是无序的。

[table-1]
key1 = "some string"
key2 = 123

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

. 禁止出现在裸键内,因为 . 是用来表示嵌套表的。每个 . 分隔的部分的命名规范与相同。

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

转换成 JSON 结构如下:

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

. 周围的空白符忽略不计,建议不要使用多余的空白符。

[a.b.c]            # 这样做最好
[ d.e.f ]          # 等价于 [d.e.f]
[ g .  h  . i ]    # 等价于 [g.h.i]
[ j . "ʞ" . 'l' ]  # 等价于 [j."ʞ".'l']

如果不想的话,可以不用声明所有的父表。TOML 知道该如何处理。

# [x] 你
# [x.y] 不需要
# [x.y.z] 这样
[x.y.z.w] # 直接这样就行

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

只要父表没有被直接定义,而且没有定义一个特定的,可以继续写入:

[a.b]
c = 1

[a]
d = 2

不能多次定义表格。这么做是不合法的。

# 别这么干!

[a]
b = 1

[a]
c = 2
# 也别这么干!

[a]
b = 1

[a.b]
c = 2

表格的名称必须是非空的。

[]     # 非法
[a.]   # 非法
[a..b] # 非法
[.b]   # 非法
[.]    # 非法

内联表

内联表提供了更紧凑语法来表示表格。它们对于分组数据特别有用,否则将很快变得冗长。内联表被包裹在 {} 的内部。在 {} 的内部,可能会出现零个或多个用 , 分隔的键值对键值对标准表中的键值对形式相同。允许所有的值类型,包括内联表

内联表的目的是出现在单行。{} 内不允许出现换行符,除非它存在于一个内。即便如此,还是强烈建议不要将一个内联表折成多行。如果你发现自己被这种欲望所困扰,那意味着你应该使用标准表

name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }

上面的内联表与下面的标准表完全相同:

[name]
first = "Tom"
last = "Preston-Werner"

[point]
x = 1
y = 2

表格数组

最后要介绍的类型是表格数组表格数组可以通过包裹在 [[]] 内的表格名来表达。使用相同的双方括号名称的表格是同一个数组的元素。表格按照书写的顺序插入。双方括号表格如果没有键值对,会被当成空表

[[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]
    color = "red"
    shape = "round"

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

  [[fruit.variety]]
    name = "granny smith"

[[fruit]]
  name = "banana"

  [[fruit.variety]]
    name = "plantain"

转换成 JSON 结构如下:

{
  "fruit": [
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "variety": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {
      "name": "banana",
      "variety": [
        { "name": "plantain" }
      ]
    }
  ]
}

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

# 非法的 TOML 文档
fruit = []

[[fruit]] # 不允许

试图定义一个标准表,且使用已经定义的数组的名称,则必须在解析时抛出错误:

# 非法的 TOML 文档
[[fruit]]
  name = "apple"

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

	# 这个表格和上面的冲突了
  [fruit.variety]
    name = "granny smith"

也可以在适当的时候使用内联表

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

文件扩展名

TOML 文件必须使用 .toml 来作为其扩展名。

(同步翻译自:TOML