Перейти к содержимому
eu_tomat

Плюсы и минусы использования Lua-кода в качестве файла конфигурации

Рекомендуемые сообщения

В этой теме я предлагаю обсудить плюсы и минусы использования Lua-кода в качестве файла конфигурации. Побудила меня к этому недавняя тема, в которой один форумчанин предложил использовать библиотеку JSON, а другой счел такое решение избыточным. У меня есть свое решение, но оно спорное. И чтобы не мусорить в той теме, предлагаю обсудить его здесь. Надеюсь, раздел я выбрал подходящий.

 

Есть два файла:

test.cfg

-- Файл конфигурации
return{
a=1;
b=2;
c=3;
--можно выполнять вычисления прямо в конфиге
bufLen=1024*1024*4;
--и даже задействовать стандартные или пользовательские функции, если они указаны в окружении
hypotenuse=sqrt(3^2+4^2);
--в тестовых целях можно спровоцировать ошибку обработки конфига:
--z=z.z;
}
test.lua

 

-- обычно достаточно указать пустое окружение
local env={} env._G=env
-- но если требуется произвести вычисления прямо в конфиге
--  с привлечением методов или даже целых библиотек,
--  то их следует добавить в окружение
local env={sqrt=math.sqrt} env._G=env
-- получение конфигурации и возможных ошибок при ее обработке
local cfg={pcall(loadfile("test.cfg",nil,env))}
-- проверка на наличие ошибок
if not cfg[1] then
  error("Ошибка в файле конфигурации\n  "..cfg[2],0)
else
  -- получение самой конфигурации (без статуса ошкбки)
  cfg=cfg[2]
end
-- таблица конфигурации готова к использованию!
for k,v in pairs(cfg)do print(k,v)end
Достоинства подхода:
  • обладает возможностями, идентичными JSON
  • не требует дополнительных библиотек
  • малый объем дополнительного кода
  • возможность указания вычисляемых параметров даже с использованием функций
Недостатки:
  • Имеется возможность Lua-инъекции
Так как пользователь сам формирует конфигурационный файл, то опасность Lua-инъекции мне представляется незначительной, тем более в рамках OpenComputers, да и вообще внутри MineCraft. Считаю даже, что такое решение с определенными ограничениями годится и для реального использования.

 

Тем не менее, интересно узнать мнение опытных программистов, какие сложности возникают при использовании lua-кода в качестве файла конфигурации.

 

Если решение где-то уже обсуждалось, прошу кинуть ссылку на него.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вполне можно использовать и Луа-конфиги. Достаточно немного обёртки. чтобы защититься от "вандализма" в конфиге.

 

Конфиг:

 

message.helloworld = "Hello, world!"
message.error = "I caught an error."

emoji.bomb = ""
emoji.heart = "♥"

answer = 42

-- what is this?
coordinates = {1,2,3}

 

Код

 

 

 

local function loadConfig()
  local base = {
    message = {},
    emoji = {},
    test = {
      test = {}
    }
  }

  local default = {
    message = {
      helloworld = "Hey",
      error = "ERR"
    },
    emoji = {
      bomb = "BOMB",
      heart = "<3"
    },
    test = {
      value = "test",
      test = {
        supervalue = "supertest"
      }
    },
    answer = 42,
    coordinates = {1, 2, 3}
  }

  local config = {}

  local function deepCopy(value)
    if type(value) ~= "table" then
      return value
    end
    local result = {}
    for k, v in pairs(value) do
      result[k] = deepCopy(v)
    end
    return result
  end

  local function createEnv(base, default, config)
    return setmetatable({}, {
      __newindex = function(self, k, v)
        if base[k] then
          return nil
        end
        if default[k] then
          config[k] = v
        end
        return nil
      end,
      __index = function(self, k)
        if base[k] then
          config[k] = config[k] or {}
          return createEnv({}, default[k], config[k])
        end
        if default[k] then
          return config[k] or deepCopy(default[k])
        end
      end
    })
  end

  local env = createEnv(base, default, config)
  loadfile("./config.cfg", "t", env)()

  local function setGet(base, default, config)
    return setmetatable({}, {
      __index = function(self, k)
        if base[k] then
          config[k] = config[k] or {}
          return setGet(base[k], default[k], config[k])
        elseif config[k] then
          return config[k]
        elseif default[k] then
          return default[k]
        end
      end
    })
  end

  return setGet(base, default, config)
end

local cfg = loadConfig()
print(cfg.message.helloworld)
print(cfg.test.test.supervalue)

 

 

 

В base находятся категории — это таблицы, которые будут иметь обработку значений из конфига.

В default, как ни странно, находятся дефолтные значения. Обязательно должны быть все таблицы из base. Кроме того, конфиг не засетит ключ, если его не будет в default.

 

После запуска код напишет это:

 

Hello, world!
supertest

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Именно этим занимается стандартный Serialization API.

Он превращает Lua табличку в текстовый Lua-код, и обратно.

С тем отличием, что не дает сериализовывать функции.

Плюс - невозможность инъкции. Минус - в конфиге нельзя ничего вычислять. В принципе, конфиг и не для этого.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Именно этим занимается стандартный Serialization API.

Он превращает Lua табличку в текстовый Lua-код, и обратно.

С тем отличием, что не дает сериализовывать функции.

Плюс - невозможность инъкции. Минус - в конфиге нельзя ничего вычислять. В принципе, конфиг и не для этого.

Хотел поспорить, что десериализация не воспринимает комментарии, которые гораздо важнее возможности вычислений в конфиге.

Но проверка выявила, что комментарии тоже воспринимаются. А это не всякий обработчик JSON умеет.

 

Upd: (текст про обнаружение ошибок перенес в следующее сообщение)

Изменено пользователем eu_tomat

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Именно этим занимается стандартный Serialization API.

Он превращает Lua табличку в текстовый Lua-код, и обратно.

С тем отличием, что не дает сериализовывать функции.

Плюс - невозможность инъкции. Минус - в конфиге нельзя ничего вычислять. В принципе, конфиг и не для этого.

Сериализованные строки требуют запятую в конце строки, которую я лично, например, постоянно забываю в файлах programs.cfg в репах своих, из-за чего клиент матерится. Мой вариант же позволяет эти запятые не ставить, что очень удобно. Ну и молчу уже про то, что в конфиге можно задавать даже функции.

Изменено пользователем Fingercomp

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

При десериализации конфига невозможно установить, в какой строке файла произошла ошибка. Это минус. При выполнении – легко.

Правда, сейчас ни мой вариант, ни вариант @@Fingercomp не способны обнаруживать любые возможные ошибки. Для полной обработки ошибок их нужно ловить как на этапе компиляции (в load), так и выполнения (в pcall) кода.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ну, да. Я с вами согласен. Запятые надо ставить, и ошибки невнятные.

 

Но это минусы уравновешиваются одним плюсом - не надо тащить дополнительной зависимости, или утяжелять программу парсером кастомного конфига.

 

К тому же конфиг - это такая штука, которую не слишком часто трогают, как вы сами знаете.

Обычно это файл, где все значения уже проставлены, и всё что требуется - это один раз допилить их напильником до оптимальных.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

в которой один форумчанин предложил использовать библиотеку JSON

А я популярен, меня уже начали упоминать везде :D

 

 

 

А это не всякий обработчик JSON умеет.

Я лишь предложил унифицированный метод хранения значений, тем более JSON все-таки более обширный и используется везде и всеми ЯПами.

Да, второй вопрос это костыли-костылевские нужно использовать чтобы записать изменения в JSON здесь.

И тут как раз удобнее юзать обычную старую добрую табличку с массивами {}

Но как говорится кому что удобно и судя по тому топику его автор предпочел использовать мой способ.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

А я популярен, меня уже начали упоминать везде :D

...

Я лишь предложил унифицированный метод хранения значений, тем более JSON все-таки более обширный и используется везде и всеми ЯПами.

Да тебя как ни упомянешь, а ник уже устарел. В следующий раз упомяну тебя как «Мистер – семь ников на неделе», станешь еще популярнее.

 

JSON хорош своей универсальностью и поддержкой во многих ЯП – это точно. Но о какой универсальности и других ЯП можно говорить, находясь в рамках СС и OC? К тому же, за универсальность приходится платить потреблением памяти и дискового пространства. Ты смотрел на размер этого модуля? Теперь сравни с объемами памяти, предоставляемыми OpenComputers. Есть стимул сберечь память для данных поважнее этой либы. Поэтому в контексте Майнкрафта единственным достоинством JSON остается синтаксический контроль, которого можно достичь гораздо более эффективными методами

 

минусы уравновешиваются одним плюсом - не надо тащить дополнительной зависимости, или утяжелять программу парсером кастомного конфига.

Тянуть зависимости для исполнения Lua-кода тоже не требуется. Расплатой за контроль синтаксиса конфига является небольшое увеличение кода:

-- формирование пустого окружения и сокрытие глобального окружения
local cfg={} cfg._G=cfg
-- получение конфигурации и возможных ошибок при ее обработке
local ld,er = loadfile("./test-cfg.cfg","",cfg)
if ld then ld,er = pcall(ld) end
-- вывод ошибок, возникших как на этапе компиляции, так и исполнения кода
if not ld then
  error("Ошибка в файле конфигурации:\n\t"..er,0)
end
-- таблица конфигурации готова к использованию!
for k,v in pairs(cfg)do print(k,v)end
Парсер @Fingercomp нужен лишь для логического контроля, хотя тоже полностью его не обеспечивает. А если логический контроль важен, то он будет использован в почти в неизменном виде как в случае исполнения конфига, как десериализации, так и JSON.

 

Что касается Lua-инъекций, то с этим, похоже, нет особых проблем. Главное, не пихать load в окружение обработки конфига.

Изменено пользователем eu_tomat

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

У меня не парсер, а кастомное окружение для скрипта и выхлопа, которое позволяет программисту не париться с дефолтными значениями везде, а юзеру — легко редактировать конфиг.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Да тебя как ни упомянешь, а ник уже устарел. В следующий раз упомяну тебя как «Мистер – семь ников на неделе», станешь еще популярнее.

 

а другой счел такое решение избыточным.

А чтож ты ник другого не вспомнил, он вроде бы как был @LeshaInc так и он и есть :D

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@@Fingercomp, мне одно не понятно: для чего задействованы все эти трюки с метаметодами? В чем преимущество такого кода перед более простым?

 

Можно же сначала тупо (через load/pcall) всосать файл в таблицу cfgUsr, а потом аккуратно перенести нужное в таблицу cfgWrk, изначально хранящую дефолтные значения. Заодно можно проверить и совпадение типа, и лишние поля в файле конфигурации.

function cfgUpd( cfgWrk, cfgUsr, prefix )
  local prefix = prefix or ""
  local type_v
  -- перебор значений таблицы cfgWrk,
  --    перенос (с удалением) соответсвующих значений из cfgUsr
  for k,v in pairs(cfgWrk)do
    if cfgUsr[k] then
      type_v = type(v)
      if type(cfgUsr[k]) ~= type_v then
        error("Ошибка в файле конфигурации: type("..prefix.."."..k..")~="..type_v,0)
      end
      if type_v~="table" then
        cfgWrk[k] = cfgUsr[k]
      else
        cfgUpd( cfgWrk[k], cfgUsr[k], prefix.."."..k )
      end
      cfgUsr[k]=nil
    end
  end
  -- перебор таблицы cfgUsr, все это ошибочные поля
  for k,v in pairs(cfgUsr)do
    error("Ошибка в файле конфигурации: недопустимое поле '"..prefix.."."..k.."'",0)
  end
end
Upd: Сегодня взглянул на свой код еще раз, он неправильно обрабатывает лишние поля во вложенных таблицах. Позже перепишу. Изменено пользователем eu_tomat

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

у меня  вообще своя либка для конфигурационных файлов) простая как двери) чем-то похожа на ini)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

у меня  вообще своя либка для конфигурационных файлов) простая как двери) чем-то похожа на ini)

Своя либка – это хорошо. У меня такой нет, и я пока только выбираю удобные для меня варианты, т. к. пока не могу учесть всех возможностей Lua. При текущем понимании мне кажется наиболее оптимальным использовать в качестве конфига возможности самого Lua-кода.

 

Можешь рассказать, насколько твоя либа компактна и удобна? Меня в основном интересует сравнение с описанным здесь методом.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Моя либа - один из простых механизмов хранения конфигурации и других данных, в теории по скорости работы не должна уступать либе сериализации, но в отличии от неё в моей либе структура файла в вполне читабельна и правима если открыть ещё текстовым редактом, там могут хранится пары ключ-значение, значение 2х типов - строка и число, и можно хранить множества таких же значений с еденичной вложенностью, в общем минимализм и легко редактировать вручную - для этого я ещё писал)

Изменено пользователем ALeXeR

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Моя либа - один из простых механизмов хранения конфигурации и других данных, в теории по скорости работы не должна уступать либе сериализации, но в отличии от неё в моей либе структура файла в вполне читабельна и правима если открыть ещё текстовым редактом, там могут хранится пары ключ-значение, значение 2х типов - строка и число, и можно хранить множества таких же значений с еденичной вложенностью, в общем минимализм и легко редактировать вручную - для этого я ещё писал)

Так и чтение конфига через его выполнение обладает всеми этими достоинствами. Кроме того, типы данных могут быть любыми, а вложенность – неограниченной. А главное, синтаксис контролируется средствами Lua, что позволяет как упростить код чтения конифга, так придерживаться везде одинаковых правил синтаксиса.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...