Ktlo

Data reader/saver v2

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