Ktlo

Data reader/saver

Sep 13th, 2015
136
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. -- Загрузка всех нужных функций
  3. local string = require "string"
  4. local global = require "_G"
  5. local table = require "table"
  6. local table = require "table"
  7. local coroutine = require "coroutine"
  8. local open = require "io".open
  9. local floor = require "math".floor
  10. local error = global.error
  11. local type = global.type
  12. local load = global.load
  13. local tonumber = global.tonumber
  14. local setmetatable = global.setmetatable
  15. local next = global.next
  16. local ipairs = global.ipairs
  17. local pcall = global.pcall
  18. local format, match, sub, gsub, find, gmatch = string.format, string.match, string.sub, string.gsub, string.find, string.gmatch
  19. local wrap, yield = coroutine.wrap, coroutine.yield
  20. local insert, concat, remove = table.insert, table.concat, table.remove
  21. string = nil
  22. coroutine = nil
  23. table = nil
  24.  
  25. local unserialize
  26. function unserialize(value, n) -- Принимает значения ключей в файле в виде строки и возвращает значения Lua
  27.     local t = sub(value, #value)
  28.     if t == 'b' then -- Определение типа boolean
  29.         return true, value ~= "0b"
  30.     elseif match(value, "^[\"'](.+)[\"']$") then -- Определение типа строки
  31.         local f = load("return "..value)
  32.         if not (f and pcall(f)) then
  33.             return f, "#"..n..": not a string format"
  34.         end
  35.         return true, f()
  36.     elseif t == 'n' then -- Определение типа число
  37.         local a = tonumber(sub(value, 1, #value-1))
  38.         if not a then
  39.             return a, "#"..n..": not a number format"
  40.         end
  41.         return true, a
  42.     elseif t == 'x' then -- Определение типа шестнадцатиричное число
  43.         local a = tonumber("0x"..sub(value, 1, #value-1))
  44.         if not a then
  45.             return a, "#"..n..": not a hex number format"
  46.         end
  47.         return true, a/1000
  48.     elseif match(value, "^{(.-)}$") then -- Определение типа массив
  49.         value = sub(value, 2, #value-1)
  50.         local k = 1
  51.         local a = { }
  52.         local c, b = 0, ""
  53.         for value in gmatch(value, "([^;,]+)") do
  54.             if find(value, "{") then
  55.                 c = c + 1
  56.             end
  57.             if find(value, "}") then
  58.                 c = c - 1
  59.             end
  60.             if c == 0 then
  61.                 b = b..value
  62.                 b = match(b, "^%s*(.-)%s*$")
  63.                 local ok, err = unserialize(b, k)
  64.                 if not ok then
  65.                     return ok, "#"..n..": in array field "..err
  66.                 end
  67.                 a[k] = err
  68.                 k = k+1
  69.                 b = ""
  70.             else
  71.                 b = b..value..","
  72.             end
  73.         end
  74.         return true, a
  75.     elseif value == "" then -- Определение типа ничего
  76.         return true, nil
  77.     end
  78.     return nil, "#"..n..": couldn't read unregistered type"
  79. end
  80.  
  81. local serialize
  82. function serialize(value, n, hex) -- Делает обратную работу функции unserialize. Аргумент hex определяет в какую СС будет кодироваться число.
  83.     local t = type(value)
  84.     if t == "boolean" then -- Определение типа boolean
  85.         return true, value == true and "1b" or "0b"
  86.     elseif t == "string" then -- Определение типа строки
  87.         return true, '"'..value:gsub("\\", "\\\\"):gsub("\t", "\\t"):gsub("\b", "\\b"):gsub("\n", "\\n"):gsub("\"", "\\\"")..'"'
  88.     elseif t == "number" then -- Определение типа число
  89.         if hex then
  90.             return true, format("%xx", floor(value*1000))
  91.         else
  92.             return true, value == floor(value) and format("%dn", value) or format("%.3fn", value)
  93.         end
  94.     elseif t == "table" then -- Определение типа массив
  95.         local s = { }
  96.         for key, value in ipairs(value) do
  97.             local ok, err = serialize(value, key, hex)
  98.             if not ok then
  99.                 return ok, err
  100.             end
  101.             s[#s+1] = err
  102.         end
  103.         s = "{"..concat(s, ", ").."}"
  104.         return true, s
  105.     elseif t == "nil" then -- Определение типа ничего
  106.         return true, ""
  107.     end
  108.     return nil, "#"..n..": couldn't code unregistered type"
  109. end
  110.  
  111. local function decode(String, n) -- Расшифровывает одну строку из файла
  112.     local key, value, comment
  113.     do -- Ниже можно увидеть адские шаблоны для строк
  114.         local a, b = match(String, ".+=.-[\"'].+[\"'].-#()(.+)$")
  115.         if not a then
  116.             a, b = match(String, "#()(.+)")
  117.         end
  118.         if a then
  119.             String = sub(String, 1, a-2)
  120.             comment = b
  121.         end
  122.     end
  123.     key, value = match(String, "^%s*(.-)%s*=%s*(.-)%s*$")
  124.     if not key then
  125.         key, value = match(String, "^%s*(.-)%s*="), ""
  126.     end
  127.     local ok = true
  128.     if key then
  129.         ok, value = unserialize(value, n)
  130.     else value = nil
  131.     end
  132.     if not ok then
  133.         return ok, value, nil, comment
  134.     end
  135.     return true, key, value, comment
  136. end
  137.  
  138. local function code(key, value, comment, hex, n) -- Конвертирует значения Lua в строку с настройкой
  139.     if key then
  140.         local ok, a = serialize(value, n, hex)
  141.         if not ok then
  142.             return ok, a
  143.         end
  144.         return true, key.." = "..a..(comment and "  #"..comment or "")
  145.     elseif comment then
  146.         return true, "#"..comment
  147.     end
  148. end
  149.  
  150. flush = function(self, hex) -- Сохраняет данные в файл
  151.     local file, err = open(self.path, "w")
  152.     if not file then
  153.         return file, err
  154.     end
  155.     local keys, values, comments, n = self.keys, self.values, self.comments, self.n
  156.     for i = 1, n do
  157.         local ok, value = code(keys[i], values[i], comments[i], hex, i)
  158.         if ok then
  159.             file:write(value)
  160.         end
  161.         if i ~= n then
  162.             file:write("\n")
  163.         end
  164.         file:flush()
  165.     end
  166.     file:close()
  167. end
  168.  
  169. -- Метатаблица для объекта с настройками
  170. local meta = {
  171.     __index = {
  172.         object = "settings"; -- Для моих будующих программ может быть полезно
  173.         save = flush;
  174.         totable = function(self) -- Трансформирует объект в обычную таблицу Lua
  175.             local keys, values, t = self.keys, self.values, { }
  176.             for i = 1, self.n do
  177.                 if keys[i] then
  178.                     t[keys[i]] = values[i]
  179.                 end
  180.             end
  181.             return t
  182.         end;
  183.         setLine = function(self, key, value, i) -- Устанавливает строку со значением
  184.             local keys, values = self.keys, self.values
  185.             i = i or self.n
  186.             if type(i) ~= "number" then
  187.                 error("bad argument #3 to method 'setLine' (number expected, got "..type(i)..")", 2)
  188.             end
  189.             self.n = self.n < i and i or self.n
  190.             if key == nil then
  191.                 keys[i] = nil
  192.                 values[i] = nil
  193.                 return
  194.             end
  195.             if type(key) == "string" then
  196.                 keys[i] = key
  197.                 values[i] = value
  198.                 return
  199.             end
  200.             error("bad argument #1 to method 'setLine' (string expected, got "..type(key)..")", 2)
  201.         end;
  202.         getLine = function(self, i) -- Возвращает всё, что храница в строке
  203.             if type(i) ~= "number" then
  204.                 error("bad argument to method 'setLine' (number expected, got "..type(i)..")", 2)
  205.             end
  206.             return self.keys[i], self.values[i], self.comments[i]
  207.         end;
  208.         set = function(self, key, value) -- Устанавливает ключу значение
  209.             local keys, values, n = self.keys, self.values, self.n
  210.             if type(key) ~= "string" then
  211.                 error("bad argument #1 to method 'setLine' (string expected, got "..type(key)..")", 2)
  212.             end
  213.             for i = 1, n do
  214.                 if key == keys[i] then
  215.                     values[i] = value
  216.                     return
  217.                 end
  218.             end
  219.             n = n+1
  220.             keys[n] = key
  221.             values[n] = value
  222.             self.n = n
  223.         end;
  224.         get = function(self, key) -- Возвращает значение ключа
  225.             local keys, values, n = self.keys, self.values, self.n
  226.             if type(key) ~= "string" then
  227.                 error("bad argument to method 'setLine' (string expected, got "..type(key)..")", 2)
  228.             end
  229.             for i = 1, n do
  230.                 if key == keys[i] then
  231.                     return values[i]
  232.                 end
  233.             end
  234.             return nil
  235.         end;
  236.         setComment = function(self, i, comment) -- Устанавливает комментарий строки
  237.             if type(i) ~= "number" then
  238.                 error("bad argument #1 to method 'setLine' (number expected, got "..type(i)..")", 2)
  239.             end
  240.             if type(comment) ~= "string" and comment ~= nil then
  241.                 error("bad argument #2 to method 'setLine' (string expected, got "..type(comment)..")", 2)
  242.             end
  243.             self.comments[i] = comment
  244.         end;
  245.         getComment = function(self, i) -- Возвращает комментарий строки
  246.             if type(i) ~= "number" then
  247.                 error("bad argument to method 'setLine' (number expected, got "..type(i)..")", 2)
  248.             end
  249.             return self.comments[i]
  250.         end;
  251.     };
  252.     __pairs = function(self) -- Возвращает итератор, который проходит по всем элементам
  253.         local keys, values, comments = self.keys, self.values, self.comments
  254.         return wrap(function()
  255.             for i = 1, self.n do
  256.                 if keys[i] then
  257.                     yield(keys[i], values[i], comments[i])
  258.                 end
  259.             end
  260.         end)
  261.     end;
  262.     __ipairs = function(self) -- Возвращает итератор, который проходит по всем строкам
  263.         local keys, values, comments = self.keys, self.values, self.comments
  264.         return wrap(function()
  265.             for i = 1, self.n do
  266.                 yield(i, keys[i], values[i], comments[i])
  267.             end
  268.         end)
  269.     end;
  270.     __len = function(self) -- Возвращает количество строк, и обнавляет поле 'n' в объекте
  271.         local n = 0
  272.         for key, value in next, self.comments do
  273.             n = n < key and key or n
  274.         end
  275.         for key, value in next, self.keys do
  276.             n = n < key and key or n
  277.         end
  278.         for key, value in next, self.values do
  279.             n = n < key and key or n
  280.         end
  281.         self.n = n
  282.         return n
  283.     end;
  284. }
  285.  
  286. return {
  287.     open = function(path) -- Возвращает объект, полученный из просканированного файла
  288.         do
  289.             local path = type(path)
  290.             if path ~= "string" then
  291.                 error("bad argument (string expected, got "..path..")", 2)
  292.             end
  293.         end
  294.         local file, err = open(path)
  295.         if not file then
  296.             return nil, err
  297.         end
  298.         local object = {
  299.             path = path;
  300.             keys = { };
  301.             values = { };
  302.             comments = { };
  303.         }
  304.         local keys, values, comments = object.keys, object.values, object.comments
  305.         local n = 0
  306.         for line in file:lines() do
  307.             n = n + 1
  308.             local ok, key, value, comment = decode(line, n)
  309.             comments[n] = comment
  310.             if key then
  311.                 keys[n] = key
  312.                 values[n] = value
  313.             end
  314.         end
  315.         object.n = n
  316.         return setmetatable(object, meta)
  317.     end;
  318.     convert = function(path, t) -- Конвертирует обычную таблицу Lua в объект настроек
  319.         t = t or { }
  320.         do
  321.             local _t = type(path)
  322.             if _t ~= "string" then
  323.                 error("bad argument #1 (string expected, got ".._t..")", 2)
  324.             end
  325.             _t = type(t)
  326.             if _t ~= "table" then
  327.                 error("bad argument #2 (table/nil expected, got ".._t..")", 2)
  328.             end
  329.         end
  330.         local i, keys, values = 0, { }, { }
  331.         for key, value in next, t do
  332.             i = i + 1
  333.             if type(key) == "string" then
  334.                 keys[i] = key
  335.                 values[i] = value
  336.             end
  337.         end
  338.         return setmetatable({
  339.             n = i;
  340.             path = path;
  341.             keys = keys;
  342.             values = values;
  343.             comments = { };
  344.         }, meta)
  345.     end;
  346.     decode = function(string, i) -- Декодер для пользователя
  347.         if type(string) ~= "string" then
  348.             error("bad argument (string expected, got "..type(string)..")", 2)
  349.         end
  350.         return decode(string, i or 1)
  351.     end;
  352.     code = function(key, value, comment, hex, i)
  353.         if type(key) ~= "string" then
  354.             error("bad argument #1 (string expected, got "..type(key)..")", 2)
  355.         end
  356.         if type(comment) ~= "string" then
  357.             error("bad argument #3 (string expected, got "..type(comment)..")", 2)
  358.         end
  359.         return code(key, value, comment, hex, i or 1)
  360.     end;
  361. }
RAW Paste Data