commit 66e459225f13cafeb70f5da6cfaf258f961bff92 Author: DarkWiiPlayer Date: Tue Aug 9 21:23:21 2022 +0200 Initial commit 🎉 * Extracted core module and default loaders from Restia * Set up luarocks * Set up busted, luacheck and luacov diff --git a/.busted b/.busted new file mode 100644 index 0000000..bf887f6 --- /dev/null +++ b/.busted @@ -0,0 +1,5 @@ +return { + _all = { + lpath = "?.lua;?/init.lua" + } +} diff --git a/config.ld b/config.ld new file mode 100644 index 0000000..df86f7d --- /dev/null +++ b/config.ld @@ -0,0 +1,3 @@ +readme = 'readme.md' +format = 'discount' +file = { 'glass.lua', 'glass' } diff --git a/glass-dev-1.rockspec b/glass-dev-1.rockspec new file mode 100644 index 0000000..24a68ae --- /dev/null +++ b/glass-dev-1.rockspec @@ -0,0 +1,27 @@ +package = "glass" +version = "dev-1" +source = { + url = "git+https://github.com/darkwiiplayer/glass" +} +description = { + homepage = "https://github.com/darkwiiplayer/glass", + license = "Unlicense" +} +dependencies = { + "lfs" +} +build = { + type = "builtin", + modules = { + glass = "glass.lua", + ["glass.cosmo"] = "glass/cosmo.lua", + ["glass.discount"] = "glass/discount.lua", + ["glass.json"] = "glass/json.lua", + ["glass.lua"] = "glass/lua.lua", + ["glass.moonhtml"] = "glass/moonhtml.lua", + ["glass.moonhtml_cosmo"] = "glass/moonhtml_cosmo.lua", + ["glass.raw"] = "glass/raw.lua", + ["glass.skooma"] = "glass/skooma.lua", + ["glass.yaml"] = "glass/yaml.lua" + } +} diff --git a/glass.lua b/glass.lua new file mode 100644 index 0000000..5d4010b --- /dev/null +++ b/glass.lua @@ -0,0 +1,51 @@ +--- Loads configurations from files on demand. +-- @author DarkWiiPlayer +-- @license Unlicense + +local lfs = require 'lfs' + +local config = {} + +local __metatable = {} +function __metatable:__index(index) + if type(index)~="string" then return nil end + local path = self.__dir..'/'..index + local attributes = lfs.attributes(path) + if attributes and attributes.mode=='directory' then + return config.bind(path, self.__loaders) + else + for _, loader in ipairs(self.__loaders) do + local result = loader(path) + if result then + rawset(self, index, result) + return result + end + end + return nil, "Could not load: "..tostring(index) + end +end + +--- Binds a table to a config directory. +-- The returned table maps keys to configurations, which are handled by +-- different "loaders". loaders are handlers that try loading a config entry in +-- a certain format and are tried sequentially until one succeeds. If no +-- loader matches, nil is returned. +-- @tparam string dir Path to the directory to look in. +-- @tparam table loaders A table of loaders to use when attempting to load a configuration entry. +-- @treturn table config A table that maps to the config directory +-- @usage +-- local config = glass.bind 'configurations' +-- main_config.foo.bar +-- -- Loads some file like foo.json or foo.yaml +-- -- in the configurations directory +function config.bind(dir, loaders) + if type(dir)~="string" then + error(string.format("bad argument #1 to '%s' (string expected, got %s)", debug.getinfo(1).name, type(loaders)), 2) + end + if type(loaders)~="table" then + error(string.format("bad argument #2 to '%s' (table expected, got %s)", debug.getinfo(1).name, type(loaders)), 2) + end + return setmetatable({__dir=dir, __loaders=loaders}, __metatable) +end + +return config diff --git a/glass/cosmo.lua b/glass/cosmo.lua new file mode 100644 index 0000000..e133906 --- /dev/null +++ b/glass/cosmo.lua @@ -0,0 +1,19 @@ +--- Loader for cosmo templates. +-- @module restia.config.cosmo + +local cosmo = require 'cosmo' +local readfile = require 'restia.config.readfile' + +--- Loads a cosmo template from a file and returns the compiled template. +-- Returns nil if no template can be found. +-- @treturn function Template +-- @function load +return function(name) + name = tostring(name) .. '.cosmo' + local text = readfile(name) + if text then + return assert(cosmo.compile(text, name)) + else + return nil + end +end diff --git a/glass/discount.lua b/glass/discount.lua new file mode 100644 index 0000000..d452eba --- /dev/null +++ b/glass/discount.lua @@ -0,0 +1,23 @@ +--- Loader for markdown files using lua-discount +-- @module restia.config.discount + +local discount = require 'discount' + +--- Loads a markdown file and converts it into HTML. +-- Returns a function that returns a static string +-- to keep compatible with restia template semantics. +-- HTML-Conversion only happens once during loading. +-- @treturn function Template +-- @function load +return function(name) + name = tostring(name) .. '.md' + local file = io.open(name) + if file then + local html = discount(file:read("*a")) + return function() + return html + end + else + return nil + end +end diff --git a/glass/json.lua b/glass/json.lua new file mode 100644 index 0000000..9278540 --- /dev/null +++ b/glass/json.lua @@ -0,0 +1,18 @@ +--- Loader for JSON-Data using Lua-CJSON +-- @module restia.config.json + +local json = require 'cjson' +local readfile = require 'restia.cofnig.readfile' + +--- Loads a JSON-File and returns a corresponding Lua table. +-- May return non-table values for invalid JSON, +-- as CJSON supports other types than Object at +-- the top-level of the JSON file. +-- @treturn table JSON-Data +-- @function load +return function(file) + local raw = readfile(file..'.json') + if raw then + return json.decode(raw) + end +end diff --git a/glass/lua.lua b/glass/lua.lua new file mode 100644 index 0000000..c42dc4a --- /dev/null +++ b/glass/lua.lua @@ -0,0 +1,13 @@ +--- Loader for Lua files +-- @module restia.config.lua + +--- Loads and compiles a Lua file and runs it. +-- Returns the result of running the Lua file. +-- If running the code immediately is not desired, +-- it has to be returned as a function. +-- @return The result of the Lua file after running it +-- @function load +return function(name) + local f = loadfile(name..'.lua') + return f and f() or nil +end diff --git a/glass/moonhtml.lua b/glass/moonhtml.lua new file mode 100644 index 0000000..27e3cbd --- /dev/null +++ b/glass/moonhtml.lua @@ -0,0 +1,18 @@ +--- Loader for MoonHTML files +-- @module restia.config.moonhtml + + +local template = require 'restia.template' + +--- Loads and compiles a moonhtml template. +-- @treturn function template +-- @function load +return function(name) + name = tostring(name) .. '.moonhtml' + local file = io.open(name) + if file then + return assert(template.loadmoon(file:read("*a"), name)) + else + return nil + end +end diff --git a/glass/raw.lua b/glass/raw.lua new file mode 100644 index 0000000..ae2f14b --- /dev/null +++ b/glass/raw.lua @@ -0,0 +1,13 @@ +--- Loader for plain files +-- @module restia.config.readfile + +--- Loads a normal file as a string. +-- @treturn string The content of the file +-- @function load +return function(path) + local f = io.open(path) + if not f then return end + local result = f:read("*a") + f:close() + return result +end diff --git a/glass/skooma.lua b/glass/skooma.lua new file mode 100644 index 0000000..113ad92 --- /dev/null +++ b/glass/skooma.lua @@ -0,0 +1,18 @@ +--- Loader for Skooma templates +-- @module restia.config.skooma + +local skooma = require 'skooma' + +--- Loads a Lua file with the Skooma environment and runs it. +-- Normally, the file should return a function +-- to follow restia template semantics. +-- @return The result of the template file. +-- @function load +return function(name) + name = tostring(name)..'.skooma' + local template = loadfile(name, "tb", skooma.default) + template = template and template() + if template then + return template + end +end diff --git a/glass/yaml.lua b/glass/yaml.lua new file mode 100644 index 0000000..155524c --- /dev/null +++ b/glass/yaml.lua @@ -0,0 +1,18 @@ +--- Loader for YAML-Data using Lua-CYAML +-- @module restia.config.yaml + +local yaml = require 'lyaml' +local readfile = require 'restia.config.readfile' + +--- Loads a YAML-File and returns a corresponding Lua table. +-- May return non-table values for invalid YAML, +-- as CYAML supports other types than Object at +-- the top-level of the YAML file. +-- @treturn table YAML-Data +-- @function load +return function(file) + local raw = readfile(file..'.yml') or readfile(file..'.yaml') + if raw then + return yaml.load(raw) + end +end diff --git a/license b/license new file mode 100644 index 0000000..b3dbff0 --- /dev/null +++ b/license @@ -0,0 +1,22 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0ae9d11 --- /dev/null +++ b/readme.md @@ -0,0 +1,33 @@ +# Glass +Makes your configs (almost) see-through. + +## About + +Glass is a Lua library that makes it easy to lazy-load configuration files into a Lua table at runtime. Its main purpose is to make accessing different configuration files "transparent" to the programmer. + +## Example +Assume the file `app/config/settings.json` exists in your project directory with the content `{"user":{"name":"User"}}` + +```lua +local glass = require 'glass' +local config = glass.bind('app/config', { + (require 'glass.json'); +}) +print(config.settings.user.name) -- prints "User" +``` + +## How it Works +A glass loader is initialised with a list of loaders and will try each of them in order in its `__index` metamethod. The first loader that is able to fetch the wanted configuration will be used. + +## Loaders +Glass offers the following loaders out of the box: +* `cosmo` loads cosmo templates +* `discount` loads markdown files\*. +* `json` loads a JSON file as a Lua table. +* `lua` loads and executes a Lua file. +* `moonhtml` loads a MoonHTML template and returns it as a function. +* `readfile` loads a file as a string. +* `skooma` loads a skooma template and returns it as a function. +* `yaml` loads a YAML file as a Lua table. + +\* For easier interoperability with other template loaders, the `discount` loader returns a static function which can be called to return the generated HTML. The markdown file is only parsed the first time. diff --git a/spec/core_spec.moon b/spec/core_spec.moon new file mode 100644 index 0000000..e8e78a9 --- /dev/null +++ b/spec/core_spec.moon @@ -0,0 +1,3 @@ +describe 'core module', -> + pending 'works as intended', -> + -- TODO: Write some tests