TOML v1.1.0
Tom 的(語意)明顯、(設定)最小化的語言(Tom's Obvious, Minimal Language)。
作者:Tom Preston-Werner、Pradyun Gedam 等人。
目標
TOML 旨在成為一種最小化的設定檔格式,因語意明顯而易於閱讀。TOML 的設計可以無歧義地對應到雜湊表。TOML 應能輕易地剖析成各種程式語言中的資料結構。
基本規則
TOML 區分大小寫。
空白字元是指 Tab 字元(U+0009)或空格(U+0020)。
換行是指 LF(U+000A)或 CRLF(U+000D U+000A)。
TOML 檔案必須是有效且以 UTF-8 編碼的 Unicode 文件。
具體而言,這表示檔案整體必須構成一個格式良好的編碼單元序列。否則必須拒絕該檔案(此為較佳做法),或依照 Unicode 規範將格式不良的位元組序列取代為 U+FFFD。
註解
井字號(#)會將該行其餘的部分標記為註解,但位於字串內時除外。
# 這是整行註解
key = "value" # 這是行尾註解
another = "# 這不是註解"
註解中不允許出現 Tab 以外的控制字元(U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。
註解應當用於檔案的人類讀者之間的溝通。剖析器不得根據註解是否存在(或其內容)而修改鍵或值。
鍵值對
TOML 文件最主要的建構單元是鍵值對。
鍵位於等號左側,值位於等號右側。鍵名與值周圍的空白字元會被忽略。鍵、等號與值必須位於同一行(不過有些值可以拆成多行撰寫)。
key = "value"
值必須是下列其中一種型別。
未指定的值是無效的。
key = # 無效
鍵值對之後必須有換行(或檔案結尾)。(例外情況請參閱內嵌表格。)
first = "Tom" last = "Preston-Werner" # 無效
鍵名
鍵可以是裸鍵、引號鍵或點分鍵。
裸鍵(bare key)只能包含 ASCII 字母、ASCII 數字、底線與連字號(A-Za-z0-9_-)。請注意,裸鍵可以只由 ASCII 數字組成,例如 1234,但一律會被解讀為字串。
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"
引號鍵(quoted key)遵循與基本字串或字面字串完全相同的規則,讓你可以使用範圍更廣的鍵名。最佳做法是除非絕對必要,否則使用裸鍵。
"127.0.0.1" = "value"
"character encoding" = "value"
"ʎǝʞ" = "value"
'key2' = "value"
'quoted "value"' = "value"
裸鍵不得為空,但空的引號鍵是允許的(不過不建議)。你不能使用多行字串來定義引號鍵。
= "no key name" # 無效
"""key""" = "not allowed" # 無效
"" = "blank" # 有效,但不建議
'' = 'blank' # 有效,但不建議
點分鍵(dotted key)是以點連接的一串裸鍵或引號鍵。這讓你可以將相似的屬性歸為一組:
name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true
若以 JSON 表示,會得到以下結構:
{
"name": "Orange",
"physical": {
"color": "orange",
"shape": "round"
},
"site": {
"google.com": true
}
}
關於點分鍵所定義的表格之詳細說明,請參閱下方的表格一節。
以點分隔的各部分周圍的空白字元會被忽略。不過,最佳做法是不要使用任何多餘的空白字元。
fruit.name = "banana" # 這是最佳做法
fruit. color = "yellow" # 等同於 fruit.color
fruit . flavor = "banana" # 等同於 fruit.flavor
縮排會被視為空白字元而被忽略。
多次定義同一個鍵是無效的。
# 請勿這樣做
name = "Tom"
name = "Pradyun"
請注意,裸鍵與引號鍵是等價的:
# 這樣行不通
spelling = "favorite"
"spelling" = "favourite"
只要某個鍵尚未被直接定義,你仍然可以為它以及其內部的名稱新增定義。
# 這會讓鍵 "fruit" 成為一個表格。
fruit.apple.smooth = true
# 因此你可以像這樣在表格 "fruit" 中新增內容:
fruit.orange = 2
# 以下是無效的
# 這會將 fruit.apple 的值定義為整數。
fruit.apple = 1
# 但接著又把 fruit.apple 當成表格來處理。
# 你無法將整數變成表格。
fruit.apple.smooth = true
不建議亂序定義點分鍵。
# 有效,但不建議
apple.type = "fruit"
orange.type = "fruit"
apple.skin = "thin"
orange.skin = "thick"
apple.color = "red"
orange.color = "orange"
# 建議做法
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" } }
字串
表示字串的方式有四種:基本字串、多行基本字串、字面字串與多行字面字串。所有字串都只能包含 Unicode 字元。
基本字串(basic string)以雙引號(")包住。除了必須跳脫的字元之外,任何 Unicode 字元都可以使用;必須跳脫的字元包括雙引號、反斜線,以及 Tab 以外的控制字元(U+0000 至 U+0008、U+000A 至 U+001F、U+007F)。
str = "I'm a string. \"You can quote me\". Name\tJos\xE9\nLocation\tSF."
為了方便,一些常用的字元有簡短的跳脫序列。
\b - 退格 (U+0008)
\t - Tab 字元 (U+0009)
\n - 換行 (U+000A)
\f - 換頁 (U+000C)
\r - 歸位 (U+000D)
\e - 跳脫(ESC) (U+001B)
\" - 雙引號 (U+0022)
\\ - 反斜線 (U+005C)
\xHH - Unicode (U+00HH)
\uHHHH - Unicode (U+HHHH)
\UHHHHHHHH - Unicode (U+HHHHHHHH)
任何 Unicode 字元都可以使用 \xHH、\uHHHH 或 \UHHHHHHHH 的形式來跳脫。跳脫碼必須是 Unicode 純量值。
請記住,所有 TOML 字串都是 Unicode 字元的序列,而非位元組序列。若要處理二進位資料,請避免使用這些跳脫碼;建議改用外部的二進位轉文字編碼策略,例如十六進位序列或 Base64,在位元組與字串之間進行轉換。
上方未列出的其他所有跳脫序列皆為保留;若使用了這些序列,TOML 應產生錯誤。
有時你需要表達一整段文字(例如翻譯檔),或想將很長的字串拆成多行。TOML 讓這件事變得很簡單。
多行基本字串(multi-line basic string)的兩側各以三個雙引號包住,並允許換行。緊接在起始分隔符號之後的換行會被去除,其餘所有的空白字元與換行字元則原封不動地保留。
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"
若要撰寫很長的字串又不想引入多餘的空白字元,請使用「行尾反斜線」。當一行中最後一個非空白字元是未經跳脫的 \ 時,它會連同其後所有的空白字元(包括換行),直到下一個非空白字元或結尾分隔符號為止,全部被去除。所有適用於基本字串的跳脫序列,也同樣適用於多行基本字串。
# 以下字串在位元組層級上完全相同:
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 字元都可以使用;必須跳脫的字元包括反斜線,以及 Tab、換行字元(LF)與歸位字元(CR)以外的控制字元(U+0000 至 U+0008、U+000B、U+000C、U+000E 至 U+001F、U+007F)。歸位字元(U+000D)只允許作為換行序列的一部分。
你可以在多行基本字串內的任何位置寫入一個雙引號或相鄰的兩個雙引號,也可以將它們緊貼著分隔符號的內側。
str4 = """Here are two quotation marks: "". Simple enough."""
# str5 = """Here are three quotation marks: """.""" # 無效
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 支援完全不允許跳脫的字面字串。
字面字串(literal string)以單引號包住。與基本字串一樣,它們必須寫在同一行:
# 所見即所得。
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
由於沒有跳脫機制,因此無法在以單引號包住的字面字串中寫入單引號。幸好 TOML 支援字面字串的多行版本,可以解決這個問題。
多行字面字串(multi-line literal string)的兩側各以三個單引號包住,並允許換行。與字面字串一樣,完全沒有任何跳脫機制。緊接在起始分隔符號之後的換行會被去除。TOML 剖析器必須以與多行基本字串相同的方式將換行正規化。
分隔符號之間的其他所有內容,都會原樣解讀,不做任何修改。
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in literal strings.
All other whitespace
is preserved.
'''
你可以在多行字面字串中的任何位置寫入一個或兩個單引號,但不允許連續出現三個或更多單引號。
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''
# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # 無效
apos15 = "Here are fifteen apostrophes: '''''''''''''''"
# 'That,' she said, 'is still pointless.'
str = ''''That,' she said, 'is still pointless.''''
字面字串中不允許出現 Tab 以外的控制字元。
整數
整數是沒有小數部分的數字。正數可以加上正號作為前綴,負數則以負號作為前綴。
int1 = +99
int2 = 42
int3 = 0
int4 = -17
對於很大的數字,你可以在數字之間使用底線來提升可讀性。每個底線的兩側都至少必須有一個數字。
int5 = 1_000
int6 = 5_349_221
int7 = 53_49_221 # 印度數字系統的分組方式
int8 = 1_2_3_4_5 # 有效,但不建議
不允許前導零。整數值 -0 與 +0 皆有效,且與沒有前綴的零相同。
非負整數值也可以用十六進位、八進位或二進位表示。在這些格式中,不允許前導的 +,且(在前綴之後)允許前導零。十六進位值不區分大小寫。數字之間允許使用底線(但前綴與數值之間不可以)。
# 十六進位,前綴為 `0x`
hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef
# 八進位,前綴為 `0o`
oct1 = 0o01234567
oct2 = 0o755 # 適用於 Unix 檔案權限
# 二進位,前綴為 `0b`
bin1 = 0b11010110
TOML 實作可以自由支援任何整數大小。建議至少接受 64 位元有號整數(從 −2^63 到 2^63−1),並且無損地處理。若整數無法被無損地表示,則必須拋出錯誤。
浮點數
浮點數由整數部分(規則與十進位整數值相同)後接小數部分及/或指數部分所組成。若同時存在小數部分與指數部分,小數部分必須位於指數部分之前。
# 小數
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01
# 指數
flt4 = 5e+22
flt5 = 1e06
flt6 = -2E-2
# 兩者並用
flt7 = 6.626e-34
小數部分是一個小數點,後接一個或多個數字。
指數部分是一個 E(大寫或小寫皆可),後接一個整數部分(規則與十進位整數值相同,但可以包含前導零)。
小數點若有使用,其兩側都至少必須有一個數字。
# 無效的浮點數
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 進行對應。
也可以表示特殊的浮點數值。它們一律為小寫。
# 無限大
sf1 = inf # 正無限大
sf2 = +inf # 正無限大
sf3 = -inf # 負無限大
# 非數值
sf4 = nan # 實際的 sNaN/qNaN 編碼由實作自行決定
sf5 = +nan # 等同於 `nan`
sf6 = -nan # 有效,實際的編碼由實作自行決定
TOML 實作可以自由支援任何精確度。建議至少支援 IEEE 754 binary64 的值。
布林值
布林值就是你熟悉的 true 與 false,且一律為小寫。
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.5-07:00
odt4 = 1979-05-27T00:32:00.999999-07:00
為了提升可讀性,你可以將日期與時間之間的 T 分隔字元換成空格字元(RFC 3339 第 5.6 節允許這麼做)。
odt4 = 1979-05-27 07:32:00Z
本規格允許對 RFC 3339 做一項例外處理:秒數可以省略,此時會假定為 :00。偏移量緊接在分鐘之後。
odt5 = 1979-05-27 07:32Z
odt6 = 1979-05-27 07:32-07:00
TOML 實作至少必須支援毫秒精確度。你可以指定更多位數的精確度,但若超出所支援的精確度,多出的位數必須被截斷,而不是四捨五入。
本地日期時間
如果省略 RFC 3339 格式日期時間中的偏移量,它就代表所給定的日期時間,且與任何偏移量或時區無關。若沒有額外的資訊,它無法被轉換為時間點。需要時,轉換為時間點的方式由實作自行決定。
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T07:32:00.5
ldt3 = 1979-05-27T00:32:00.999999
秒數可以省略,此時會假定為 :00。
ldt3 = 1979-05-27T07:32
TOML 實作至少必須支援毫秒精確度。你可以指定更多位數的精確度,但若超出所支援的精確度,多出的位數必須被截斷,而不是四捨五入。
本地日期
如果只包含 RFC 3339 格式日期時間中的日期部分,它就代表整個那一天,且與任何偏移量或時區無關。
ld1 = 1979-05-27
本地時間
如果只包含 RFC 3339 格式日期時間中的時間部分,它就代表一天中的那個時刻,且與任何特定日期、偏移量或時區無關。
lt1 = 07:32:00
lt2 = 00:32:00.5
lt3 = 00:32:00.999999
秒數可以省略,此時會假定為 :00。
lt3 = 07:32
TOML 實作至少必須支援毫秒精確度。你可以指定更多位數的精確度,但若超出所支援的精確度,多出的位數必須被截斷,而不是四捨五入。
陣列
陣列是以中括號包住的有序值。空白字元會被忽略。元素以逗號分隔。陣列可以包含鍵值對中允許的各種資料型別的值,不同型別的值也可以混用。
integers = [ 1, 2, 3 ]
colors = [ "red", "yellow", "green" ]
nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ]
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
string_array = [ "all", 'strings', """are the same""", '''type''' ]
# 允許混合型別的陣列
numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]
contributors = [
"Foo Bar <foo@example.com>",
{ name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" }
]
陣列可以跨越多行。最後一個值之後允許有結尾逗號(亦稱尾隨逗號)。值、逗號與右中括號之前,可以出現任意數量的換行與註解。陣列的值與逗號之間的縮排會被視為空白字元而被忽略。
integers2 = [
1, 2, 3
]
integers3 = [
1,
2, # 這樣是可以的
]
表格
表格(亦稱雜湊表或字典)是鍵值對的集合。表格由標頭定義,標頭是獨占一行、以中括號包住的內容。你可以將標頭與陣列區分開來,因為陣列永遠只會是值。
[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" } } } }
鍵周圍的空白字元會被忽略。不過,最佳做法是不要使用任何多餘的空白字元。
[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] # 才能讓這個運作
[x] # 之後再定義上層表格也可以
允許空表格,它們就只是內部沒有任何鍵值對。
與鍵一樣,你不能多次定義同一個表格。這麼做是無效的。
# 請勿這樣做
[fruit]
apple = "red"
[fruit]
orange = "orange"
# 這樣也不行
[fruit]
apple = "red"
[fruit.apple]
texture = "smooth"
不建議亂序定義表格。
# 有效,但不建議
[fruit.apple]
[animal]
[fruit.orange]
# 建議做法
[fruit.apple]
[fruit.orange]
[animal]
頂層表格(亦稱根表格)從文件的開頭開始,到第一個表格標頭(或檔案結尾)之前結束。與其他表格不同,它沒有名稱,也無法被重新定位。
# 頂層表格開始。
name = "Fido"
breed = "pug"
# 頂層表格結束。
[owner]
name = "Regina Dogman"
member_since = 1999-08-04
點分鍵會為最後一個鍵部分之前的每個鍵部分建立並定義一個表格。任何這類表格的所有鍵值對,都必須全部定義在目前的 [table] 標頭之下,或(若出現在所有標頭之前)定義在根表格中,或定義在單一內嵌表格內。
fruit.apple.color = "red"
# 定義一個名為 fruit 的表格
# 定義一個名為 fruit.apple 的表格
fruit.apple.taste.sweet = true
# 定義一個名為 fruit.apple.taste 的表格
# fruit 與 fruit.apple 已經建立過了
由於表格不能被定義多次,因此不允許使用 [table] 標頭重新定義這類表格。同樣地,也不允許使用點分鍵重新定義已經以 [table] 形式定義過的表格。不過,[table] 形式可以用來在由點分鍵定義的表格中定義子表格。
[fruit]
apple.color = "red"
apple.taste.sweet = true
# [fruit.apple] # 無效
# [fruit.apple.taste] # 無效
[fruit.apple.texture] # 你可以新增子表格
smooth = true
內嵌表格
內嵌表格提供更精簡的語法來表示表格。它們特別適合用來表示成組的巢狀資料,否則這類資料很快就會變得冗長。
內嵌表格完全定義在大括號 { 與 } 之內。大括號內可以出現零個或多個以逗號分隔的鍵值對。鍵值對的形式與標準表格中的鍵值對相同。所有型別的值都允許,包括內嵌表格。
內嵌表格可以在同一行中放置多個鍵值對,也可以分散在不同行。最後一個鍵值對之後允許有結尾逗號(亦稱尾隨逗號)。
name = { first = "Tom", last = "Preston-Werner" }
point = {x=1, y=2}
animal = { type.name = "pug" }
contact = {
personal = {
name = "Donald Duck",
email = "donald@duckburg.com",
},
work = {
name = "Coin cleaner",
email = "donald@ScroogeCorp.com",
},
}
上述內嵌表格與以下標準表格的定義相同:
[name]
first = "Tom"
last = "Preston-Werner"
[point]
x = 1
y = 2
[animal]
type.name = "pug"
[contact.personal]
name = "Donald Duck"
email = "donald@duckburg.com"
[contact.work]
name = "Coin cleaner"
email = "donald@ScroogeCorp.com"
內嵌表格是完全自足的,所有的鍵與子表格都定義在其內部。不能在大括號之外新增鍵與子表格。
[product]
type = { name = "Nail" }
# type.edible = false # 無效
同樣地,也不能使用內嵌表格為已定義的表格新增鍵或子表格。
[product]
type.name = "Nail"
# type = { edible = false } # 無效
表格陣列
最後一個尚未說明的語法,可用來撰寫表格陣列。做法是使用名稱以雙中括號包住的標頭。該標頭的第一個實例會定義陣列及其第一個表格元素,之後的每個實例都會在該陣列中建立並定義一個新的表格元素。表格會依照遇到的順序插入陣列。
[[product]]
name = "Hammer"
sku = 738594937
[[product]] # 陣列中的空表格
[[product]]
name = "Nail"
sku = 284758393
color = "gray"
若以 JSON 表示,會得到以下結構。
{
"product": [
{ "name": "Hammer", "sku": 738594937 },
{},
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}
任何對表格陣列的參照,指向的都是該陣列中最近定義的表格元素。這讓你可以在最近的表格內定義子表格,甚至是子表格陣列。
[[fruits]]
name = "apple"
[fruits.physical] # 子表格
color = "red"
shape = "round"
[[fruits.varieties]] # 巢狀的表格陣列
name = "red delicious"
[[fruits.varieties]]
name = "granny smith"
[[fruits]]
name = "banana"
[[fruits.varieties]]
name = "plantain"
上述 TOML 會對應到以下 JSON。
{
"fruits": [
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"varieties": [{ "name": "red delicious" }, { "name": "granny smith" }]
},
{
"name": "banana",
"varieties": [{ "name": "plantain" }]
}
]
}
如果表格或表格陣列的父層是陣列元素,則必須先定義該元素,才能定義其子層。任何顛倒此順序的嘗試,都必須在剖析階段產生錯誤。
# 無效的 TOML 文件
[fruit.physical] # 子表格,但它應該屬於哪個父元素?
color = "red"
shape = "round"
[[fruit]] # 剖析器一旦發現 "fruit" 是陣列而不是表格,
# 就必須拋出錯誤
name = "apple"
嘗試對靜態定義的陣列附加元素時,即使該陣列是空的,也必須在剖析階段產生錯誤。
# 無效的 TOML 文件
fruits = []
[[fruits]] # 不允許
嘗試定義與既有陣列同名的一般表格時,必須在剖析階段產生錯誤;同樣地,嘗試將一般表格重新定義為陣列時,也必須在剖析階段產生錯誤。
# 無效的 TOML 文件
[[fruits]]
name = "apple"
[[fruits.varieties]]
name = "red delicious"
# 無效:此表格與前面的表格陣列衝突
[fruits.varieties]
name = "granny smith"
[fruits.physical]
color = "red"
shape = "round"
# 無效:此表格陣列與前面的表格衝突
[[fruits.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 檔案 提供。