diff --git a/samples/array-member.lua b/samples/array-member.lua new file mode 100644 index 0000000..48304a4 --- /dev/null +++ b/samples/array-member.lua @@ -0,0 +1,23 @@ +return { + ["arr"] = { + [1] = { + ["prop1"] = { + ["subprop1"] = "test", + ["subprop2"] = { + ["test2"] = "test", + ["test3"] = {} + } + } + }, + [2] = { + ["prop2"] = { + ["subprop3"] = "test.test" + } + }, + [3] = { + ["prop3"] = { + ["subprop4"] = 5 + } + } + } +} diff --git a/samples/array-member.yaml b/samples/array-member.yaml new file mode 100644 index 0000000..fdb6533 --- /dev/null +++ b/samples/array-member.yaml @@ -0,0 +1,11 @@ +--- +arr: + - prop1: + subprop1: test + subprop2: + test2: test + test3: {} + - prop2: + subprop3: test.test + - prop3: + subprop4: 5 \ No newline at end of file diff --git a/samples/comments-first.lua b/samples/comments-first.lua new file mode 100644 index 0000000..201375a --- /dev/null +++ b/samples/comments-first.lua @@ -0,0 +1,3 @@ +return { + ["number"] = 123, +} diff --git a/samples/comments-first.yaml b/samples/comments-first.yaml new file mode 100644 index 0000000..9ab2619 --- /dev/null +++ b/samples/comments-first.yaml @@ -0,0 +1,2 @@ +#first line is comment +number: 123 diff --git a/samples/comments.lua b/samples/comments.lua new file mode 100644 index 0000000..901a3f8 --- /dev/null +++ b/samples/comments.lua @@ -0,0 +1,20 @@ +return { + ["key"] = {"value line 1", "value line 2", "value line 3"}, + ["key2"] = 30, + ["key3"] = "String value", + ["key4"] = "#not a comment", + ["key5"] = false, + ["key6"] = 123, + ["key7"] = { + ["key8"] = "value", + ["key9"] = "not#comments" + }, + ["key10"] = {1, 2}, + ["key11"] = "[3, 4] it is another string", + ["key12"] = "[5, 6] StrinG", + ["key13"] = {7, 8}, + ["key14"] = "[9, 10] StrinG", + ["key15"] = "[11, 12] StrinG# notcomment", + ["key16"] = {13, 14}, + ["key17"] = "[15, 16] string" +} diff --git a/samples/comments.yaml b/samples/comments.yaml new file mode 100644 index 0000000..4566861 --- /dev/null +++ b/samples/comments.yaml @@ -0,0 +1,26 @@ +--- +key: #comment after id + - value line 1 + #comment before list item + - value line 2 +#comment + #multi + #line + - value line 3 #Comment after list item +key2: 30 # comment 5 +key3: "String value" # comment after string with quotes +key4: "#not a comment" +key5: false # Comment boolean +key6: 123 # Comment number +key7: + key8: value # Comment simple string + key9: not#comments #Comment simple string with # inside and multiple # + +key10: [1, 2] +key11: [3, 4] it is another string # and after comment +key12: [5, 6] StrinG # Another language comment: кирилица ёпрст +key13: [7, 8] +key14: [9, 10] StrinG # Comment with spaces and #more #more # comment +key15: [11, 12] StrinG# notcomment # comment +key16: [13, 14] #comment +key17: [15, 16] string \ No newline at end of file diff --git a/samples/dates.lua b/samples/dates.lua index 5a0a790..00b23c9 100644 --- a/samples/dates.lua +++ b/samples/dates.lua @@ -1,8 +1,8 @@ return { - ["date0"] = 1305907200, - ["date1"] = 1305907200, - ["date2"] = 1293897600, - ["date3"] = 1298282160, - ["date4"] = 1303335300, - ["date5"] = 1307018730 + ["date0"] = os.time{year=2011, month=5, day=21, hour=0, min=0, sec=0, isdst=false}, + ["date1"] = os.time{year=2011, month=5, day=21, hour=0, min=0, sec=0, isdst=false}, + ["date2"] = os.time{year=2011, month=1, day=2, hour=0, min=0, sec=0, isdst=false}, + ["date3"] = os.time{year=2011, month=2, day=21, hour=17, min=56, sec=0, isdst=false}, + ["date4"] = os.time{year=2011, month=4, day=21, hour=5, min=35, sec=0, isdst=false}, + ["date5"] = os.time{year=2011, month=6, day=2, hour=20, min=45, sec=30, isdst=false} } diff --git a/samples/empty.lua b/samples/empty.lua new file mode 100644 index 0000000..6ad1635 --- /dev/null +++ b/samples/empty.lua @@ -0,0 +1,2 @@ +-- can not return nil, so use 0 instead. +return 0 diff --git a/samples/empty.yaml b/samples/empty.yaml new file mode 100644 index 0000000..e69de29 diff --git a/samples/hash.yaml b/samples/hash.yaml index c45447c..5c74e62 100644 --- a/samples/hash.yaml +++ b/samples/hash.yaml @@ -1,20 +1,29 @@ --- - users: - tj: - name: tj - age: 23 - email: 'tj@vision-media.ca' - bob: - name: 'bob' - age: 27 - ted: { name: ted, age: 32, email: ted@tedtalks.com } - country: - name: Österreich - website: http://en.wikipedia.org/wiki/Austria - space: - description: space, the final frontier - brackets: - square: Square [brackets] can go in the middle of strings - squiggle: Squiggle {brackets} can also go in the middle of strings! - extrasquare: [Scratch that] brackets can go at the beginning as long as they close and have text after. - extrasquiggle: {Scratch that} squigs can go at the beginning also! +users: + tj: + name: tj + age: 23 + email: 'tj@vision-media.ca' + bob: + name: 'bob' + age: 27 + ted: { name: ted, age: 32, email: ted@tedtalks.com } + + + +country: + name: Österreich + website: http://en.wikipedia.org/wiki/Austria + +space: + description: space, the final frontier + +brackets: + square: Square [brackets] can go in the middle of strings + + + squiggle: Squiggle {brackets} can also go in the middle of strings! + + extrasquare: [Scratch that] brackets can go at the beginning as long as they close and have text after. + + extrasquiggle: {Scratch that} squigs can go at the beginning also! diff --git a/samples/invoice.lua b/samples/invoice.lua index 99c8c5a..09e6d12 100644 --- a/samples/invoice.lua +++ b/samples/invoice.lua @@ -10,7 +10,7 @@ Suite #292", ["family"] = "Dumars", ["given"] = "Chris" }, - ["date"] = 980179200, + ["date"] = os.time{year=2001, month=1, day=23, hour=0, min=0, sec=0, isdst=false}, ["invoice"] = 34843, ["ship-to"] = { ["address"] = { diff --git a/samples/log.lua b/samples/log.lua index 6ea27a6..51603ed 100644 --- a/samples/log.lua +++ b/samples/log.lua @@ -1,5 +1,5 @@ return { - ["Time"] = 1006498902, + ["Time"] = os.time{year=2001, month=11, day=23, hour=10, min=1, sec=42, isdst=false} , ["User"] = "ed", ["Warning"] = "This is an error message\ for the log file" diff --git a/samples/log2.lua b/samples/log2.lua index b5fbe08..576462f 100644 --- a/samples/log2.lua +++ b/samples/log2.lua @@ -1,5 +1,5 @@ return { - ["Time"] = 1006498951, + ["Time"] = os.time{year=2001, month=11, day=23, hour=10, min=2, sec=31, isdst=false}, ["User"] = "ed", ["Warning"] = "A slightly different error\ message." diff --git a/samples/log3.lua b/samples/log3.lua index d2d88a1..2e6b37c 100644 --- a/samples/log3.lua +++ b/samples/log3.lua @@ -1,5 +1,5 @@ return { - ["Date"] = 1006498997, + ["Date"] = os.time{year=2001, month=11, day=23, hour=10, min=3, sec=17, isdst=false}, ["Fatal"] = "Unknown variable \"bar\"", ["Stack"] = { [1] = { diff --git a/samples/log4.lua b/samples/log4.lua index f5f0022..79ff713 100644 --- a/samples/log4.lua +++ b/samples/log4.lua @@ -1,5 +1,5 @@ return { - ["Date"] = 1006498997, + ["Date"] = os.time{year=2001, month=11, day=23, hour=10, min=3, sec=17, isdst=false}, ["Fatal"] = "Unknown variable \"bar\"", ["Stack"] = { [1] = { diff --git a/samples/negative-prop.lua b/samples/negative-prop.lua new file mode 100644 index 0000000..cfa03de --- /dev/null +++ b/samples/negative-prop.lua @@ -0,0 +1,4 @@ +return { + prop1 = -1, + prop2 = -1.5, +} \ No newline at end of file diff --git a/samples/negative-prop.yaml b/samples/negative-prop.yaml new file mode 100644 index 0000000..f9922f5 --- /dev/null +++ b/samples/negative-prop.yaml @@ -0,0 +1,3 @@ +--- +prop1: -1 +prop2: -1.5 \ No newline at end of file diff --git a/spec/test_spec.lua b/spec/test_spec.lua index 16a5dd6..a27fe89 100644 --- a/spec/test_spec.lua +++ b/spec/test_spec.lua @@ -29,7 +29,34 @@ describe('Parsing in', function() it(file, function() local data = yaml.eval(readAll("samples/"..file..".yaml")) local answer = require("samples."..file) - assert.are.same(answer, data) + if answer == 0 then + assert.is.Nil(data) + else + assert.are.same(answer, data) + end end) end end) + +describe('dump', function() + local expected = { + total = 3, + title = "very good!", + list = { + {item = "good", id = 3}, + {item = "item2", id = 4, sub = { + no = 34, + te = "haha", + it = { + "this is false", + "aother", + {some=1, at=4} + } + }}, + {b = {}}, + } + } + local str = yaml.dump(expected) + local data = yaml.eval(str) + assert.are.same(expected, data) +end) diff --git a/yaml.lua b/yaml.lua index 55211dc..2871a6b 100644 --- a/yaml.lua +++ b/yaml.lua @@ -1,44 +1,46 @@ -local table_print_value -table_print_value = function(value, indent, done) +---yaml.lua +--LUA YAML parser, based on js-lua, fast and tiny. +--Although this implementation does not currently support the entire YAML specification, +--feel free to fork the project and submit a patch :) +-- https://github.com/exosite/lua-yaml + +local Generator = {} +function Generator.new (self) + return self +end + +Generator.tablePrintValue =function (self, value, indent, done) indent = indent or 0 done = done or {} - if type(value) == "table" and not done [value] then - done [value] = true + if type(value) =="table" and not done[value] then + done[value] = true + if next(value) == nil then return '{}' end local list = {} for key in pairs (value) do - list[#list + 1] = key + list[#list+1] = key end table.sort(list, function(a, b) return tostring(a) < tostring(b) end) - local last = list[#list] - local rep = "{\n" - local comma - for _, key in ipairs (list) do - if key == last then - comma = '' - else - comma = ',' - end + local rep ="\n" + for _, key in ipairs(list) do local keyRep - if type(key) == "number" then - keyRep = key + if type(key) =="number" then + keyRep = "- " else - keyRep = string.format("%q", tostring(key)) + keyRep = tostring(key)..": " end - rep = rep .. string.format( - "%s[%s] = %s%s\n", - string.rep(" ", indent + 2), + rep = rep..string.format( + "%s%s%s\n", + string.rep(" ", indent), keyRep, - table_print_value(value[key], indent + 2, done), - comma + self:tablePrintValue(value[key], indent+2, done) ) end - rep = rep .. string.rep(" ", indent) -- indent it - rep = rep .. "}" + rep = rep..string.rep(" ", indent) - done[value] = false + done[value] =false return rep elseif type(value) == "string" then return string.format("%q", value) @@ -47,10 +49,18 @@ table_print_value = function(value, indent, done) end end -local table_print = function(tt) - print('return '..table_print_value(tt)) +local function trimBlankLine(str) + local lines = {} + for s in str:gmatch("[^\r\n]+") do + s = string.gsub(s, "%s+$", "") + if #s > 0 then + table.insert(lines, s) + end + end + return table.concat(lines, "\n") end + local table_clone = function(t) local clone = {} for k,v in pairs(t) do @@ -92,13 +102,14 @@ function Parser.new (self, tokens) return self end -local exports = {version = "1.2"} +local exports = {version = "1.3"} local word = function(w) return "^("..w..")([%s$%c])" end local tokens = { - {"comment", "^#[^\n]*"}, - {"indent", "^\n( *)"}, + {"yaml-version", "^%%YAML ([0-9%.]*)"}, + {"comment", "^[\n ]*#[^\n]*"}, + {"indent", "^[\n]+( *)"}, {"space", "^ +"}, {"true", word("enabled"), const = true, value = true}, {"true", word("true"), const = true, value = true}, @@ -127,17 +138,22 @@ local tokens = { {"string", "^%b{} *[^,%c]+", noinline = true}, {"{", "^{"}, {"}", "^}"}, - {"string", "^%b[] *[^,%c]+", noinline = true}, + -- Any letter/punctuation after '[...]', before '#' is string (after '#' comment) + {"string", "^(%b[] *[^%c #]+[^%c]-) #[^\n]*", noinline = true}, + {"string", "^(%b[] *[^%c #]+[^%c]+)", noinline = true}, -- Any letter/punctuation after '[...]' is string {"[", "^%["}, {"]", "^%]"}, + {"string", "^-[^%s]+", noinline = true}, {"-", "^%-", noinline = true}, {":", "^:"}, {"pipe", "^(|)(%d*[+%-]?)", sep = "\n"}, {"pipe", "^(>)(%d*[+%-]?)", sep = " "}, {"id", "^([%w][%w %-_]*)(:[%s%c])"}, - {"string", "^[^%c]+", noinline = true}, + {"string", "^([^%c]-)( #)[^\n]+", noinline = true}, --String with " #comment" + {"string", "^([^%c]+)", noinline = true}, {"string", "^[^,%]}%c ]+"} }; + exports.tokenize = function (str) local token local row = 0 @@ -157,13 +173,12 @@ exports.tokenize = function (str) end if #captures > 0 then - captures.input = str:sub(0, 25) + captures.input = str:sub(0, 50) token = table_clone(tokens[i]) token[2] = captures local str2 = str:gsub(tokens[i][2], "", 1) token.raw = str:sub(1, #str - #str2) str = str2 - if token[1] == "{" or token[1] == "[" then inline = true elseif token.const then @@ -177,6 +192,7 @@ exports.tokenize = function (str) -- Trim token[2][1] = string_trim(token[2][1]) elseif token[1] == "string" then + token[2][1] =string_trim(token[2][1]) -- Finding numbers local snip = token[2][1] if not token.force_text then @@ -184,9 +200,8 @@ exports.tokenize = function (str) token[1] = "number" end end - elseif token[1] == "comment" then - ignore = true; + ignore = true elseif token[1] == "indent" then row = row + 1 inline = false @@ -194,7 +209,6 @@ exports.tokenize = function (str) if indentAmount == 0 then indentAmount = #token[2][1] end - if indentAmount ~= 0 then indents = (#token[2][1] / indentAmount); else @@ -315,29 +329,33 @@ Parser.parse = function (self) } push(self.parse_stack, c) - if c.token[1] == "doc" then - result = self:parseDoc() - elseif c.token[1] == "-" then - result = self:parseList() - elseif c.token[1] == "{" then - result = self:parseInlineHash() - elseif c.token[1] == "[" then - result = self:parseInlineList() - elseif c.token[1] == "id" then - result = self:parseHash() - elseif c.token[1] == "string" then - result = self:parseString("\n") - elseif c.token[1] == "timestamp" then - result = self:parseTimestamp() - elseif c.token[1] == "number" then - result = tonumber(self:advanceValue()) - elseif c.token[1] == "pipe" then - result = self:parsePipe() - elseif c.token.const == true then - self:advanceValue(); - result = c.token.value - else - error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input)) + if c.token then + if c.token[1] == "yaml-version" then + result = self:parseYamlVersion() + elseif c.token[1] == "doc" then + result = self:parseDoc() + elseif c.token[1] == "-" then + result = self:parseList() + elseif c.token[1] == "{" then + result = self:parseInlineHash() + elseif c.token[1] == "[" then + result = self:parseInlineList() + elseif c.token[1] == "id" then + result = self:parseHash() + elseif c.token[1] == "string" then + result = self:parseString("\n") + elseif c.token[1] == "timestamp" then + result = self:parseTimestamp() + elseif c.token[1] == "number" then + result = tonumber(self:advanceValue()) + elseif c.token[1] == "pipe" then + result = self:parsePipe() + elseif c.token.const == true then + self:advanceValue(); + result = c.token.value + else + error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input)) + end end pop(self.parse_stack) @@ -350,9 +368,19 @@ Parser.parse = function (self) if ref then self.refs[ref] = result end + return result end +Parser.parseYamlVersion = function (self) + local version = tonumber(self:advanceValue()) + if version~=1.1 then + error("Unsupported YAML protocol version, has: "..version..", need: 1.1") + end + self:accept("yaml-version") + return self:parse() +end + Parser.parseDoc = function (self) self:accept("doc") return self:parse() @@ -367,10 +395,14 @@ Parser.inline = function (self) local inline = {} local i = 0 - while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do + while + self:peek(i) and not self:peekType("indent", i) and not self:peekType("doc", i) + and current.row == self:peek(i).row + do inline[self:peek(i)[1]] = true i = i - 1 end + return inline, -i end @@ -468,7 +500,6 @@ end Parser.parseHash = function (self, hash) hash = hash or {} local indents = 0 - if self:isInline() then local id = self:advanceValue() self:expect(":", "expected semi-colon after id") @@ -487,7 +518,7 @@ Parser.parseHash = function (self, hash) while self:peekType("id") do local id = self:advanceValue() - self:expect(":","expected semi-colon after id") + self:expect(":", "expected semi-colon after id") self:ignoreSpace() hash[id] = self:parse() self:ignoreSpace(); @@ -560,23 +591,28 @@ Parser.parseInlineList = function (self) end Parser.parseTimestamp = function (self) - local capture = self:advance()[2] - + local capture =self:advance()[2] return os.time{ - year = capture[1], - month = capture[2], - day = capture[3], - hour = capture[4] or 0, - min = capture[5] or 0, - sec = capture[6] or 0, - isdst = false, - } - os.time{year=1970, month=1, day=1, hour=8} + year =capture[1], + month =capture[2], + day =capture[3], + hour =(capture[4] or 0) + (capture[7] or 0), + min =(capture[5] or 0) + (capture[8] or 0), + sec =capture[6] or 0, + isdst =false, + } end -exports.eval = function (str) - return Parser:new(exports.tokenize(str)):parse() +exports.eval = function(str) + local parser = Parser:new(exports.tokenize(str)) + return parser:parse() end -exports.dump = table_print +exports.dump = function(table) + local generator = Generator:new() + local result = generator:tablePrintValue(table) + result = trimBlankLine(result) + return result +end return exports