From 8bdf26e359319bdf303e35f722995eac45205743 Mon Sep 17 00:00:00 2001 From: Bart van Strien Date: Fri, 11 Oct 2013 17:47:44 +0200 Subject: [PATCH] Add comment preservation (section-accurate comments) and order preservation (both of sections and keys), meaning that if the comments are at the top of sections or at the top of the file, and the output table's metatable isn't modified, the ini file written out should exactly match the one read in, ignoring whitespace --- inifile/inifile.lua | 102 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/inifile/inifile.lua b/inifile/inifile.lua index 1eed2fa..94cb11a 100644 --- a/inifile/inifile.lua +++ b/inifile/inifile.lua @@ -1,4 +1,4 @@ --- Copyright 2011 Bart van Strien. All rights reserved. +-- Copyright 2011-2013 Bart van Strien. All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are -- permitted provided that the following conditions are met: @@ -53,34 +53,116 @@ function inifile.parse(name, backend) backend = backend or defaultBackend local t = {} local section + local comments = {} + local sectionorder = {} + local cursectionorder + for line in backends[backend].lines(name) do + + -- Section headers local s = line:match("^%[([^%]]+)%]$") if s then section = s t[section] = t[section] or {} + cursectionorder = {name = section} + table.insert(sectionorder, cursectionorder) end + + -- Comments + s = line:match("^;(.+)$") + if s then + local commentsection = section or comments + comments[commentsection] = comments[commentsection] or {} + table.insert(comments[commentsection], s) + end + + -- Key-value pairs local key, value = line:match("^(%w+)%s-=%s-(.+)$") if tonumber(value) then value = tonumber(value) end if value == "true" then value = true end if value == "false" then value = false end if key and value ~= nil then t[section][key] = value + table.insert(cursectionorder, key) end end - return t + + -- Store our metadata in the __inifile field in the metatable + return setmetatable(t, { + __inifile = { + comments = comments, + sectionorder = sectionorder, + } + }) end function inifile.save(name, t, backend) backend = backend or defaultBackend - local contents = "" - for section, s in pairs(t) do - local sec = ("[%s]\n"):format(section) - for key, value in pairs(s) do - sec = sec .. ("%s=%s\n"):format(key, tostring(value)) - end - contents = contents .. sec .. "\n" + local contents = {} + + -- Get our metadata if it exists + local metadata = getmetatable(t) + local comments, sectionorder + + if metadata then metadata = metadata.__inifile end + if metadata then + comments = metadata.comments + sectionorder = metadata.sectionorder end - return backends[backend].write(name, contents) + + -- If there are comments before sections, + -- write them out now + if comments and comments[comments] then + for i, v in ipairs(comments[comments]) do + table.insert(contents, (";%s"):format(v)) + end + table.insert(contents, "") + end + + local function writevalue(section, key) + local value = section[key] + table.insert(contents, ("%s=%s"):format(key, tostring(value))) + end + + local function writesection(section, order) + local s = t[section] + table.insert(contents, ("[%s]"):format(section)) + + -- Write our comments out again, sadly we have only achieved + -- section-accuracy so far + if comments and comments[section] then + for i, v in ipairs(comments[section]) do + table.insert(contents, (";%s"):format(v)) + end + end + + -- Write the key-value pairs with optional order + if order then + for _, v in ipairs(order) do + writevalue(s, v) + end + else + for i, _ in pairs(s) do + writevalue(s, i) + end + end + + -- Newline after the section + table.insert(contents, "") + end + + -- Write the sections, with optional order + if sectionorder then + for _, v in ipairs(sectionorder) do + writesection(v.name, v) + end + else + for i, _ in pairs(contents) do + writesection(i) + end + end + + return backends[backend].write(name, table.concat(contents, "\n")) end return inifile