lyaml.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. -- Transform between YAML 1.1 streams and Lua table representations.
  2. -- Written by Gary V. Vaughan, 2013
  3. --
  4. -- Copyright (c) 2013-2015 Gary V. Vaughan
  5. --
  6. -- Permission is hereby granted, free of charge, to any person obtaining a
  7. -- copy of this software and associated documentation files (the "Software"),
  8. -- to deal in the Software without restriction, including without limitation
  9. -- the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. -- and/or sell copies of the Software, and to permit persons to whom the
  11. -- Software is furnished to do so, subject to the following conditions:
  12. --
  13. -- The above copyright notice and this permission notice shall be included in
  14. -- all copies or substantial portions of the Software.
  15. --
  16. -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19. -- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21. -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  22. -- DEALINGS IN THE SOFTWARE.
  23. --
  24. -- Portions of this software were inspired by an earlier LibYAML binding by
  25. -- Andrew Danforth <acd@weirdness.net>
  26. local lib = "yaml.libyaml"
  27. if WIN32 then
  28. if jit then lib = lib..'jit' end
  29. elseif OSX then
  30. lib = lib..'osx'
  31. else
  32. local p = io.popen('uname -i')
  33. if p:read('*a'):find('64') then lib = lib..'64' end
  34. p:close()
  35. end
  36. local yaml = require(lib)
  37. local TAG_PREFIX = "tag:yaml.org,2002:"
  38. local null = setmetatable ({}, { _type = "LYAML null" })
  39. local function isnull (x)
  40. return (getmetatable (x) or {})._type == "LYAML null"
  41. end
  42. -- Metatable for Dumper objects.
  43. local dumper_mt = {
  44. __index = {
  45. -- Emit EVENT to the LibYAML emitter.
  46. emit = function (self, event)
  47. return self.emitter.emit (event)
  48. end,
  49. -- Look up an anchor for a repeated document element.
  50. get_anchor = function (self, value)
  51. local r = self.anchors[value]
  52. if r then
  53. self.aliased[value], self.anchors[value] = self.anchors[value], nil
  54. end
  55. return r
  56. end,
  57. -- Look up an already anchored repeated document element.
  58. get_alias = function (self, value)
  59. return self.aliased[value]
  60. end,
  61. -- Dump ALIAS into the event stream.
  62. dump_alias = function (self, alias)
  63. return self:emit {
  64. type = "ALIAS",
  65. anchor = alias,
  66. }
  67. end,
  68. -- Dump MAP into the event stream.
  69. dump_mapping = function (self, map)
  70. local alias = self:get_alias (map)
  71. if alias then
  72. return self:dump_alias (alias)
  73. end
  74. self:emit {
  75. type = "MAPPING_START",
  76. anchor = self:get_anchor (map),
  77. style = "BLOCK",
  78. }
  79. for k, v in pairs (map) do
  80. self:dump_node (k)
  81. self:dump_node (v)
  82. end
  83. return self:emit {type = "MAPPING_END"}
  84. end,
  85. -- Dump SEQUENCE into the event stream.
  86. dump_sequence = function (self, sequence)
  87. local alias = self:get_alias (sequence)
  88. if alias then
  89. return self:dump_alias (alias)
  90. end
  91. self:emit {
  92. type = "SEQUENCE_START",
  93. anchor = self:get_anchor (sequence),
  94. style = "BLOCK",
  95. }
  96. for _, v in ipairs (sequence) do
  97. self:dump_node (v)
  98. end
  99. return self:emit {type = "SEQUENCE_END"}
  100. end,
  101. -- Dump a null into the event stream.
  102. dump_null = function (self)
  103. return self:emit {
  104. type = "SCALAR",
  105. value = "~",
  106. plain_implicit = true,
  107. quoted_implicit = true,
  108. style = "PLAIN",
  109. }
  110. end,
  111. -- Dump VALUE into the event stream.
  112. dump_scalar = function (self, value)
  113. local alias = self:get_alias (value)
  114. if alias then
  115. return self:dump_alias (alias)
  116. end
  117. local anchor = self:get_anchor (value)
  118. local itsa = type (value)
  119. local style = "PLAIN"
  120. if value == "true" or value == "false" or
  121. value == "yes" or value == "no" or value == "~" or
  122. (type (value) ~= "number" and tonumber (value) ~= nil) then
  123. style = "SINGLE_QUOTED"
  124. elseif itsa == "number" or itsa == "boolean" then
  125. value = tostring (value)
  126. end
  127. return self:emit {
  128. type = "SCALAR",
  129. anchor = anchor,
  130. value = value,
  131. plain_implicit = true,
  132. quoted_implicit = true,
  133. style = style,
  134. }
  135. end,
  136. -- Decompose NODE into a stream of events.
  137. dump_node = function (self, node)
  138. local itsa = type (node)
  139. if isnull (node) then
  140. return self:dump_null ()
  141. elseif itsa == "string" or itsa == "boolean" or itsa == "number" then
  142. return self:dump_scalar (node)
  143. elseif itsa == "table" then
  144. if #node > 0 then
  145. return self:dump_sequence (node)
  146. else
  147. return self:dump_mapping (node)
  148. end
  149. else -- unsupported Lua type
  150. error ("cannot dump object of type '" .. itsa .. "'", 2)
  151. end
  152. end,
  153. -- Dump DOCUMENT into the event stream.
  154. dump_document = function (self, document)
  155. self:emit {type = "DOCUMENT_START"}
  156. self:dump_node (document)
  157. return self:emit {type = "DOCUMENT_END"}
  158. end,
  159. },
  160. }
  161. -- Emitter object constructor.
  162. local function Dumper (anchors)
  163. local t = {}
  164. for k, v in pairs (anchors or {}) do t[v] = k end
  165. local object = {
  166. anchors = t,
  167. aliased = {},
  168. emitter = yaml.emitter (),
  169. }
  170. return setmetatable (object, dumper_mt)
  171. end
  172. local function dump (documents, anchors)
  173. local dumper = Dumper (anchors)
  174. dumper:emit { type = "STREAM_START", encoding = "UTF8" }
  175. for _, document in ipairs (documents) do
  176. dumper:dump_document (document)
  177. end
  178. local ok, stream = dumper:emit { type = "STREAM_END" }
  179. return stream
  180. end
  181. -- Metatable for Parser objects.
  182. local parser_mt = {
  183. __index = {
  184. -- Return the type of the current event.
  185. type = function (self)
  186. return tostring (self.event.type)
  187. end,
  188. -- Raise a parse error.
  189. error = function (self, errmsg)
  190. error (string.format ("%d:%d: %s", self.mark.line,
  191. self.mark.column, errmsg), 0)
  192. end,
  193. -- Save node in the anchor table for reference in future ALIASes.
  194. add_anchor = function (self, node)
  195. if self.event.anchor ~= nil then
  196. self.anchors[self.event.anchor] = node
  197. end
  198. end,
  199. -- Fetch the next event.
  200. parse = function (self)
  201. local ok, event = pcall (self.next)
  202. if not ok then
  203. -- if ok is nil, then event is a parser error from libYAML
  204. self:error (event:gsub (" at document: .*$", ""))
  205. end
  206. self.event = event
  207. self.mark = {
  208. line = self.event.start_mark.line + 1,
  209. column = self.event.start_mark.column + 1,
  210. }
  211. return self:type ()
  212. end,
  213. -- Construct a Lua hash table from following events.
  214. load_map = function (self)
  215. local map = {}
  216. self:add_anchor (map)
  217. while true do
  218. local key = self:load_node ()
  219. if key == nil then break end
  220. local value, event = self:load_node ()
  221. if value == nil then
  222. self:error ("unexpected " .. self:type () .. "event")
  223. end
  224. map[key] = value
  225. end
  226. return map
  227. end,
  228. -- Construct a Lua array table from following events.
  229. load_sequence = function (self)
  230. local sequence = {}
  231. self:add_anchor (sequence)
  232. while true do
  233. local node = self:load_node ()
  234. if node == nil then break end
  235. sequence[#sequence + 1] = node
  236. end
  237. return sequence
  238. end,
  239. -- Construct a primitive type from the current event.
  240. load_scalar = function (self)
  241. local value = self.event.value
  242. local tag = self.event.tag
  243. if tag then
  244. tag = tag:match ("^" .. TAG_PREFIX .. "(.*)$")
  245. if tag == "str" then
  246. -- value is already a string
  247. elseif tag == "int" or tag == "float" then
  248. value = tonumber (value)
  249. elseif tag == "bool" then
  250. value = (value == "true" or value == "yes")
  251. end
  252. elseif self.event.style == "PLAIN" then
  253. if value == "~" then
  254. value = null
  255. elseif value == "true" or value == "yes" then
  256. value = true
  257. elseif value == "false" or value == "no" then
  258. value = false
  259. else
  260. local number = tonumber (value)
  261. if number then value = number end
  262. end
  263. end
  264. self:add_anchor (value)
  265. return value
  266. end,
  267. load_alias = function (self)
  268. local anchor = self.event.anchor
  269. if self.anchors[anchor] == nil then
  270. self:error ("invalid reference: " .. tostring (anchor))
  271. end
  272. return self.anchors[anchor]
  273. end,
  274. load_node = function (self)
  275. local dispatch = {
  276. SCALAR = self.load_scalar,
  277. ALIAS = self.load_alias,
  278. MAPPING_START = self.load_map,
  279. SEQUENCE_START = self.load_sequence,
  280. MAPPING_END = function () end,
  281. SEQUENCE_END = function () end,
  282. DOCUMENT_END = function () end,
  283. }
  284. local event = self:parse ()
  285. if dispatch[event] == nil then
  286. self:error ("invalid event: " .. self:type ())
  287. end
  288. return dispatch[event] (self)
  289. end,
  290. },
  291. }
  292. -- Parser object constructor.
  293. local function Parser (s)
  294. local object = {
  295. anchors = {},
  296. mark = { line = 0, column = 0 },
  297. next = yaml.parser (s),
  298. }
  299. return setmetatable (object, parser_mt)
  300. end
  301. local function load (s, all)
  302. local documents = {}
  303. local parser = Parser (s)
  304. if parser:parse () ~= "STREAM_START" then
  305. error ("expecting STREAM_START event, but got " .. parser:type (), 2)
  306. end
  307. while parser:parse () ~= "STREAM_END" do
  308. local document = parser:load_node ()
  309. if document == nil then
  310. error ("unexpected " .. parser:type () .. " event")
  311. end
  312. if parser:parse () ~= "DOCUMENT_END" then
  313. error ("expecting DOCUMENT_END event, but got " .. parser:type (), 2)
  314. end
  315. -- save document
  316. documents[#documents + 1] = document
  317. -- reset anchor table
  318. parser.anchors = {}
  319. end
  320. return all and documents or documents[1]
  321. end
  322. --[[ ----------------- ]]--
  323. --[[ Public Interface. ]]--
  324. --[[ ----------------- ]]--
  325. local M = {
  326. dump = dump,
  327. load = load,
  328. null = null,
  329. _VERSION = yaml.version,
  330. }
  331. return M