System.Configuration
March 12, 2019 ยท View on GitHub
The configurations are name-type pairs that'd be registered in anywhere with or without special handlers, so we can parse the config settings in one place for the whole system.
The configurations are usually divided into groups, so here is a group of configurations:
Configurations = {
Debug = Boolean,
LogLevel = Logger.LogLevel,
LogHandler = Callable,
ErrorHandler = Callable,
Application = {
Root = String,
Version = Number,
}
}
The config settings are also divided into groups, the preivous configurations would match a group of config settings like :
Config = {
Debug = false,
LogLevel = Logger.LogLevel.Info,
LogHandler = print,
ErrorHandler = print,
Application = {
Root = "/lib",
Version = 10010,
}
}
The configurations are names with require types, and the config settings are names with require type values.
The configurations in a group may be registered from several parts, and the config settings should be distributed to their special handlers.
System.Configuration.ConfigSection
The config sections are used as containers for configurations. The config settings can be parsed by it, then the section'd raise events to distribute the settings.
Start with an example:
require "PLoop" (function(_ENV)
import "System.Configuration"
-- Configurations
section = ConfigSection()
section.Field.Debug = Boolean
section.Field.LogLevel = Logger.LogLevel
section.Field.LogHandler = Callable
section.Field.ErrorHandler = Callable
-- Configurations.Application
local appsec = section.Section.Application
appsec.Field.Root = String
appsec.Field.Version = Number
-- Field handler for section
function section:OnFieldParse(name, val, ...)
print("[SECTION]", name, val, ...)
end
-- Config handler for section
function section:OnParse(config, ...)
print("[SECTION FIN]", config.Debug, config.LogLevel, ...)
end
-- Field handler for app section
function appsec:OnFieldParse(name, val, ...)
print("[APP]", name, val, ...)
end
-- Config handler for app section
function appsec:OnParse(config, ...)
print("[APP FIN]", config.Root, config.Version, ...)
end
-- Parse the config settings
section:ParseConfig({
Debug = false,
LogLevel = Logger.LogLevel.Info,
LogHandler = print,
ErrorHandler = print,
Application = {
Root = "/lib",
Version = 10010,
}
}, "PARSE CONFIG")
end)
The result should be like
[SECTION] Debug false PARSE CONFIG
[SECTION] LogLevel 2 PARSE CONFIG
[SECTION] LogHandler function: 00C77308 PARSE CONFIG
[SECTION] ErrorHandler function: 00C77308 PARSE CONFIG
[APP] Root /lib PARSE CONFIG
[APP] Version 10010 PARSE CONFIG
[APP FIN] /lib 10010 PARSE CONFIG
[SECTION FIN] false 2 PARSE CONFIG
Config Section
Each config section is a table contains several name-type paris, so the config value should match the type of the same name. That's the field settings like section.Field.Debug = Boolean.
Each config section can contains other config sections, we can get new or existed section through section.Section.XXXXXX.
We can travese the section through GetFields and GetSections method, they'll return the fields or sections with the order, the first defined would be the first returned:
require "PLoop" (function(_ENV)
import "System.Configuration"
section = ConfigSection()
section.Field.Debug = Boolean
section.Field.LogLevel = Logger.LogLevel
section.Field.LogHandler = Callable
section.Field.ErrorHandler = Callable
local appsec = section.Section.Application
appsec.Field.Root = String
appsec.Field.Version = Number
-- Print the structure
function printSection(sec, indent, name)
indent = indent and (indent .. " " ) or ""
if name then
print(indent .. name .. " = {")
else
print(indent .. "{")
end
-- Traverse the fields
for name, type in sec:GetFields() do
print(indent .. " " .. name .. " = " .. type .. ",")
end
-- Traverse the sections
for name, subsec in sec:GetSections() do
printSection(subsec, indent, name)
end
if indent == "" then
print("}")
else
print(indent .. "},")
end
end
--- {
--- Debug = System.Boolean,
--- LogLevel = System.Logger.LogLevel,
--- LogHandler = System.Callable,
--- ErrorHandler = System.Callable,
--- Application = {
--- Root = System.String,
--- Version = System.Number,
--- },
--- }
printSection(section)
end)
Parse Config Settings
The ParseConfig 's arguments is defined like
__Arguments__{ Table, Any * 0 }
function ParseConfig(self, config, ...)
end
It'd receive a config table and any other arguments would be passed through the events. In the previous example, the "PARSE CONFIG" is passed to the event handlers.
In the ParseConfig, name-value pairs would be distributed by OnFieldParse attribute, and the parsed config would be distributed by OnParse at last.
__ConfigSection__
Using event handler is not a self-document code style, and since the configurations are defined all over the places, we need a clear and self-document code style to declare the configurations.
The Configurations system provided an attribute __ConfigSection__ for functions.
Let's have an example:
require "PLoop" (function(_ENV)
import "System.Configuration"
class "HtmlRender" (function(_ENV)
__Static__() property "ConfigSection" { set = false, default = Configuration.ConfigSection() }
__Static__() property "Config" {
type = Table,
throwable = true,
set = function(self, config)
if config then
local ret, msg = HtmlRender.ConfigSection:ParseConfig(config)
if msg then throw(Struct.GetErrorMessage(msg, "HtmlRender.Config")) end
end
end,
}
__Static__() property "NoLineBreak" { type = Boolean }
__Static__() property "NoIndent" { type = Boolean }
__Static__() property "DebugMode" { type = Boolean }
end)
-------------------------------------
-- Register config section or field setting
-------------------------------------
__ConfigSection__( HtmlRender.ConfigSection.Render, { nolinebreak = Boolean, noindent = Boolean } )
function HtmlRenderConfig(config, ...)
print("CONFIG", config.nolinebreak, config.noindent)
HtmlRender.NoLineBreak = config.nolinebreak
HtmlRender.NoIndent = config.noindent
end
__ConfigSection__( HtmlRender.ConfigSection, "debug", Boolean)
function HtmlRenderDebug(field, value, ...)
print("DEBUG", field, value)
HtmlRender.DebugMode = value
end
-------------------------------------
-- Usage
-------------------------------------
-- CONFIG false true
-- DEBUG debug true
HtmlRender.Config = {
Render = {
nolinebreak = false,
noindent = true,
},
debug = true,
}
-- false true true
print(HtmlRender.NoLineBreak, HtmlRender.NoIndent, HtmlRender.DebugMode)
end)
So we only decalre the config section and where to parse the config in the class, register the config section or config in any other places.
Then we ccan provide the config in any config file and let the config handlers do the real jobs.
BTW, the HtmlRender.ConfigSection.Render is a shortcut for HtmlRender.ConfigSection.Section.Render, through the config section's __index meta-method, it can access its fields or sections, since the Render is not existed in the config section, it'll be created as a sub config section automatically.