Ktlo

Data reader/saver v4

Sep 30th, 2015
90
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2.         Сохранение настроек в 0.4
  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+1
  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.         self.n = self.n < i and i or self.n
  263.     end;
  264.     getComment = function(self, i) -- Возвращает комментарий строки
  265.         if type(i) ~= "number" then
  266.             error("bad argument to method 'setLine' (number expected, got "..type(i)..")", 2)
  267.         end
  268.         return self.comments[i]
  269.     end;
  270. }
  271.  
  272. -- Метатаблица для объекта с настройками
  273. local meta = {
  274.     __index = function(self, key)
  275.         local r = methods[key]
  276.         if r then return r end
  277.         if type(key) ~= "string" then
  278.             error("bad key word (string expected, got "..type(key)..")", 2)
  279.         end
  280.         return get(self, key)
  281.     end;
  282.     __newindex = function(self, key, value)
  283.         if type(key) ~= "string" then
  284.             error("bad key word (string expected, got "..type(key)..")", 2)
  285.         end
  286.         return set(self, key, value)
  287.     end;
  288.     __pairs = function(self) -- Возвращает итератор, который проходит по всем элементам
  289.         local keys, values, comments = self.keys, self.values, self.comments
  290.         return wrap(function()
  291.             for i = 1, self.n do
  292.                 if keys[i] then
  293.                     yield(keys[i], values[i], comments[i])
  294.                 end
  295.             end
  296.         end)
  297.     end;
  298.     __ipairs = function(self) -- Возвращает итератор, который проходит по всем строкам
  299.         local keys, values, comments = self.keys, self.values, self.comments
  300.         return wrap(function()
  301.             for i = 1, self.n do
  302.                 yield(i, keys[i], values[i], comments[i])
  303.             end
  304.         end)
  305.     end;
  306.     __len = function(self) -- Возвращает количество строк, и обнавляет поле 'n' в объекте
  307.         local n = 0
  308.         for key, value in next, self.comments do
  309.             n = n < key and key or n
  310.         end
  311.         for key, value in next, self.keys do
  312.             n = n < key and key or n
  313.         end
  314.         for key, value in next, self.values do
  315.             n = n < key and key or n
  316.         end
  317.         self.n = n
  318.         return n
  319.     end;
  320.     __tostring = function(self) -- Возвращает тип объекта и путь до файла
  321.         return "settings: ("..self.path..")"
  322.     end;
  323. }
  324.  
  325. return {
  326.     open = function(path) -- Возвращает объект, полученный из просканированного файла
  327.         do
  328.             local path = type(path)
  329.             if path ~= "string" then
  330.                 error("bad argument (string expected, got "..path..")", 2)
  331.             end
  332.         end
  333.         local file, err = open(path)
  334.         if not file then
  335.             return nil, err
  336.         end
  337.         local object = {
  338.             path = path;
  339.             keys = { };
  340.             values = { };
  341.             comments = { };
  342.         }
  343.         local keys, values, comments = object.keys, object.values, object.comments
  344.         local n = 0
  345.         for line in file:lines() do
  346.             n = n + 1
  347.             local ok, key, value, comment = decode(line, n)
  348.             comments[n] = comment
  349.             if key then
  350.                 keys[n] = key
  351.                 values[n] = value
  352.             end
  353.         end
  354.         object.n = n
  355.         file:close()
  356.         return setmetatable(object, meta)
  357.     end;
  358.     convert = function(path, t) -- Конвертирует обычную таблицу Lua в объект настроек
  359.         t = t or { }
  360.         do
  361.             local _t = type(path)
  362.             if _t ~= "string" then
  363.                 error("bad argument #1 (string expected, got ".._t..")", 2)
  364.             end
  365.             _t = type(t)
  366.             if _t ~= "table" then
  367.                 error("bad argument #2 (table/nil expected, got ".._t..")", 2)
  368.             end
  369.         end
  370.         local i, keys, values = 0, { }, { }
  371.         for key, value in next, t do
  372.             i = i + 1
  373.             if type(key) == "string" then
  374.                 keys[i] = key
  375.                 values[i] = value
  376.             end
  377.         end
  378.         return setmetatable({
  379.             n = i;
  380.             path = path;
  381.             keys = keys;
  382.             values = values;
  383.             comments = { };
  384.         }, meta)
  385.     end;
  386.     decode = function(string, i) -- Декодер для пользователя
  387.         if type(string) ~= "string" then
  388.             error("bad argument (string expected, got "..type(string)..")", 2)
  389.         end
  390.         return decode(string, i or 1)
  391.     end;
  392.     code = function(key, value, comment, hex, i)
  393.         if type(key) ~= "string" then
  394.             error("bad argument #1 (string expected, got "..type(key)..")", 2)
  395.         end
  396.         if type(comment) ~= "string" then
  397.             error("bad argument #3 (string expected, got "..type(comment)..")", 2)
  398.         end
  399.         return code(key, value, comment, hex, i or 1)
  400.     end;
  401. }
RAW Paste Data