TOML

本文是对 https://github.com/toml-lang/toml 的翻译,增加了一些个人理解,还有一些实验中遇到的问题处理。

TOML

TOML是Github联合创始人Tom Preston-Werner(记住这个名字,他很牛X, 他的github地址 )2013年创建的语言。TOMLTom’s Obvious, Minimal Language的缩写, 对应中文为Tom的浅显、最小化的语言(这是什么鬼?)。

简单的说,这是一种类似于INIXMLJSONYAML的数据格式。

TOML当前的版本是v0.5.0,是一个比较稳定的版本,v1.0.0版本会向后尽可能的兼容这个版本。所以,建议所有实现尽量兼容v0.5.0,这样在v1.0.0发布的时候,会少很多麻烦。

设计目标

TOML的设计目标是作为一种语意浅显的(Obvious),易读的,最小化(Minimal)的配置文件格式。TOML被设计成可以无歧义地被映射为哈希表,可以被多种语言解析。

示例

# 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 = [ 8001, 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"
]

规范

  • TOML是大小写敏感的
  • TOML文件必须是UTF-8编码的
  • 空白符可以是空格(0x20)和tab(0x09)
  • 换行符可以是LF(0x0A)和CRLF(0x0D0A)

注释

#之后的内容被标记为注释。

# This is a full-line comment
key = "value" # This is a comment at the end of a line

键值对

TOML文档最基本的构成是键/值对。键名在等号的左边,值在右边,键名和键值周围的空格会被忽略。键、等号和值必须在同一行(不过有些值可以跨多行)

键值是必须的,可以是以下类型:

  • 字符串
  • 整数
  • 浮点数
  • 布尔值
  • 日期时刻
  • 数组
  • 行内表

键名

键名可以是裸键,也可以被引号包裹,被.号分隔。

裸键

裸键只能包含ASCII字母,ASCII数字,下划线和短横线(A-Za-z0-9_-),可以是纯数学,解析时,这样的键被当做纯数学字符串来处理。

key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"

引号键

引号键(Quoted keys)遵循基本字符串或字面量字符串相同的规则,可以定义更广泛的键名。但是,除非必须使用这样的定义方式才会用,一般情况下裸键就够了。

"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

点号键

这种方式v0.5.0貌似不支持用于key=value的形式式,而是只用于表名定义。官方文档没有更新,这里直接翻译了官方的文档。

如果在.toml文件中使用例如physical.shape = "round"这样的定义,Golang API会报如下错误:

Near line 0 (last key parsed ''): bare keys cannot contain '.'
exit status 1

可以改为如下的定义

[physical]
color = "b"
shape = "c"

点号键(Dotted keys)是一通过点相连的裸键或引号键。一般用于相似属性,比如数据库用驱动程序名,连接地址,用户名,密码可以使用相同的以点号分隔的字符串做为键名。

name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true

上面的示例等价于JSON

{
  "name": "Orange",
  "physical": {
    "color": "orange",
    "shape": "round"
  },
  "site": {
    "google.com": true
  }
}

点分隔符周围的空白会被忽略,但是不建议有任何空格。

键名是不可重复的,同时,只要键名没有被直接定,就可以使用。

a.b.c = 1
a.d = 2

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

字符串

TOML有四种方式来表示字符串:基本字符串多行基本字符串字面量多行字面量。所有字符串都只能包含有效的UTF-8字符 (U+0000 to U+001F, U+007F)。

基本字符串(Basic strings)

基本字符串由引号包裹。任何Unicode字符都可以使用,除了那些必须转义的:引号,反斜杠,以及控制字符。

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)

任何Unicode字符都可以用\uXXXX\UXXXXXXXX的形式来转义。转义码必须是有效的Unicode标量值。

所有上面未列出的其它转义序列都是保留的,如果使用,TOML应当生成一个错误。

多行基本字符串(Multi-line basic strings)

多行基本字符串由三个引号包裹,允许换行,开头引号后的那个换行会被去除。其它空白和换行符会被保留。

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"

如果某行内容很长,又想写成多行方便阅读,可以在行尾使用\。当一行的最后一个非空白字符是\时,TOML会把它以及它后面的所有空白(包括换行)移除,直到下一个非空白字符或结束引号。

# 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+001F,U+007F)。

字面量字符串(Literal strings)

字面量字符串由单引号包裹。类似于基本字符串,只能表现为单行,由于没有转义,无法在由单引号包裹的字面量字符串中写入单引号

# 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*>'

多行字面量字符串(Multi-line literal strings)

多行字面量字符串由三个单引号来包裹,允许换行。类似于字面量字符串,无论任何转义都不可用。允许换行,开头引号后的那个换行会被去除,所有其它内容会原样对待。

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

整数

整数是纯数字。正数可以有加号前缀,负数的前缀是减号。可接受范围为64位(记作长整型) [−9,223,372,036,854,775,808 至 9,223,372,036,854,775,807]

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

可以在数字之间用下划线来增强可读性。每个下划线两侧必须至少有一个数字。

int5 = 1_000
int6 = 5_349_221
int7 = 1_2_3_4_5     # VALID but discouraged

数字前面不可以增加0-0+0是有效的,同于无前缀的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

浮点数

浮点数应当实现IEEE 75464位双精度值。浮点数由整数部分(遵从与十进制整数值相同的规则)后跟小数部分(小数部分是小数点后跟一个或多个数字)和/或指数部分(指数部分是一个 E(大小写都可以)后跟一个整数)组成。如果小数部分和指数部分兼有,那小数部分必须在指数部分前面。

# fractional
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01

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

# both
flt7 = 6.626e-34

与整数相似,可以使用下划线来增强可读性。

flt8 = 224_617.445_991_228

特殊浮点数表示

# 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

布尔值

布尔值只有truefalse,全部小写。

bool1 = true
bool2 = false

日期与时间

偏移日期与时间(Offset Date-Time)

特定的时间可以使用指定了偏移量的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

数组

数组是一组被方括号包裹的相同类型的值。元素由逗号分隔,元素之间的空白会被忽略,元素的数据类型必须一致(不同写法的字符串被认为是相同的类型,不同元素类型的数组也被认为是同样的数组类型)。

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

数组也可以跨多行,最后一个值后面可以有逗号(称为尾逗号),值和结束括号前可以存在任意数量的换行和注释。

arr7 = [
  1, 2, 3
]

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

表(又称为哈希表或者字典)是一组键值对的集合。

表名由方括号包裹,单独一行出现,表名的规则与键名相同。

[table]

在表名下方至下一个表或文件结束之间的键值对,都是这个表的键值对。表不保证这些键值对的顺序。

[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" } } } }

TOML可以定义空表,但是不允许重复定义表名,同时,只要一个表名还没有被直接定义过,就可以对它和它下属的键名赋值。

# DO NOT DO THIS

[a]
b = 1

[a]
c = 2
# DO NOT DO THIS EITHER

[a]
b = 1

[a.b]
c = 2

行内表

行内表使用一种更紧凑的语法来表示表格。

行内表由花括号包裹,在花括号中,可以出现零个或更多逗号分隔的键值对。键值对采用与标准表中的键值对相同的形式。

行内表不允许换行(多行字符串除外),即使是这样,也强烈不建议让一个行内表跨多行(不然干嘛叫行内表?)。

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"

表数组

表数组使用双方括号来表示,具有相同方括号名的表将会成为该数组内的成员。元素的顺序就是元素在文件中出现顺序。没有键值对的双方括号表将被视为一个空表。

[[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" }
      ]
    }
  ]
}

不要试图向一个静态定义的数组追加内容,那一定会报错的。即使数组为空或者类型兼容。

# INVALID TOML DOC
fruit = []

[[fruit]] # Not allowed

可以适当使用行内表

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

文件扩展名

TOML文件使用 .toml 扩展名。在互联网上传输 TOML 文件时,MIME类型为application/toml

参考链接

https://github.com/toml-lang/toml/
https://github.com/toml-lang/toml/blob/master/versions/cn/toml-v0.5.0.md