Ktlo

Data reader/saver v3

Sep 28th, 2015
94
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2.         Сохранение настроек в 0.3
  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 'get' (string expected, got "..type(key)..")", 2)
  251.         end
  252.         return get(self, key)
  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.     __tostring = function(self) -- Возвращает тип объекта и путь до файла
  320.         return "settings: ("..self.path..")"
  321.     end;
  322. }
  323.  
  324. return {
  325.     open = function(path) -- Возвращает объект, полученный из просканированного файла
  326.         do
  327.             local path = type(path)
  328.             if path ~= "string" then
  329.                 error("bad argument (string expected, got "..path..")", 2)
  330.             end
  331.         end
  332.         local file, err = open(path)
  333.         if not file then
  334.             return nil, err
  335.         end
  336.         local object = {
  337.             path = path;
  338.             keys = { };
  339.             values = { };
  340.             comments = { };
  341.         }
  342.         local keys, values, comments = object.keys, object.values, object.comments
  343.         local n = 0
  344.         for line in file:lines() do
  345.             n = n + 1
  346.             local ok, key, value, comment = decode(line, n)
  347.             comments[n] = comment
  348.             if key then
  349.                 keys[n] = key
  350.                 values[n] = value
  351.             end
  352.         end
  353.         object.n = n
  354.         return setmetatable(object, meta)
  355.     end;
  356.     convert = function(path, t) -- Конвертирует обычную таблицу Lua в объект настроек
  357.         t = t or { }
  358.         do
  359.             local _t = type(path)
  360.             if _t ~= "string" then
  361.                 error("bad argument #1 (string expected, got ".._t..")", 2)
  362.             end
  363.             _t = type(t)
  364.             if _t ~= "table" then
  365.                 error("bad argument #2 (table/nil expected, got ".._t..")", 2)
  366.             end
  367.         end
  368.         local i, keys, values = 0, { }, { }
  369.         for key, value in next, t do
  370.             i = i + 1
  371.             if type(key) == "string" then
  372.                 keys[i] = key
  373.                 values[i] = value
  374.             end
  375.         end
  376.         return setmetatable({
  377.             n = i;
  378.             path = path;
  379.             keys = keys;
  380.             values = values;
  381.             comments = { };
  382.         }, meta)
  383.     end;
  384.     decode = function(string, i) -- Декодер для пользователя
  385.         if type(string) ~= "string" then
  386.             error("bad argument (string expected, got "..type(string)..")", 2)
  387.         end
  388.         return decode(string, i or 1)
  389.     end;
  390.     code = function(key, value, comment, hex, i)
  391.         if type(key) ~= "string" then
  392.             error("bad argument #1 (string expected, got "..type(key)..")", 2)
  393.         end
  394.         if type(comment) ~= "string" then
  395.             error("bad argument #3 (string expected, got "..type(comment)..")", 2)
  396.         end
  397.         return code(key, value, comment, hex, i or 1)
  398.     end;
  399. }
RAW Paste Data