Ktlo

Data reader/saver v1.0

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