Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

Introduce global config #46

Merged
merged 7 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions lib/Config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
--[[
Exposes an interface to set global configuration values for Roact.

Configuration can only occur once, and should only be done by an application
using Roact, not a library.

Any keys that aren't recognized will cause errors. Configuration is only
intended for configuring Roact itself, not extensions or libraries.

Configuration is expected to be set immediately after loading Roact. Setting
configuration values after an application starts may produce unpredictable
behavior.
]]

-- Every valid configuration value should be non-nil in this table.
local defaultConfig = {
-- Enables storage of `debug.traceback()` values on elements for debugging.
["elementTracing"] = false,
}

-- Build a list of valid configuration values up for debug messages.
local defaultConfigKeys = {}
for key in pairs(defaultConfig) do
table.insert(defaultConfigKeys, key)
end

--[[
Merges two tables together into a new table.
]]
local function join(a, b)
local new = {}

for key, value in pairs(a) do
new[key] = value
end

for key, value in pairs(b) do
new[key] = value
end

return new
end

local Config = {}

function Config.new()
local self = {}

-- Once configuration has been set, we record a traceback.
-- That way, if the user mistakenly calls `set` twice, we can point to the
-- first place it was called.
self._lastConfigTraceback = nil

self._currentConfig = defaultConfig

-- We manually bind these methods here so that the Config's methods can be
-- used without passing in self, since they eventually get exposed on the
-- root Roact object.
self.set = function(...)
return Config.set(self, ...)
end

self.getValue = function(...)
return Config.getValue(self, ...)
end

self.reset = function(...)
return Config.reset(self, ...)
end

return self
end

function Config.set(self, configValues)
if self._lastConfigTraceback then
local message = (
"Global configuration can only be set once. Configuration was already set at:%s"
):format(
self._lastConfigTraceback
)

error(message, 3)
end

-- We use 3 as our traceback and error level because all of the methods are
-- manually bound to 'self', which creates an additional stack frame we want
-- to skip through.
self._lastConfigTraceback = debug.traceback("", 3)

-- Validate values without changing any configuration.
-- We only want to apply this configuration if it's valid!
for key, value in pairs(configValues) do
if defaultConfig[key] == nil then
local message = (
"Invalid global configuration key %q (type %s). Valid configuration keys are: %s"
):format(
tostring(key),
typeof(key),
table.concat(defaultConfigKeys, ", ")
)

error(message, 3)
end

-- Right now, all configuration values must be boolean.
if typeof(value) ~= "boolean" then
local message = (
"Invalid value %q (type %s) for global configuration key %q. Valid values are: true, false"
):format(
tostring(value),
typeof(value),
tostring(key)
)

error(message, 3)
end
end

-- Assign all of the (validated) configuration values in one go.
self._currentConfig = join(self._currentConfig, configValues)
end

function Config.getValue(self, key)
if defaultConfig[key] == nil then
local message = (
"Invalid global configuration key %q (type %s). Valid configuration keys are: %s"
):format(
tostring(key),
typeof(key),
table.concat(defaultConfigKeys, ", ")
)

error(message, 3)
end

return self._currentConfig[key]
end

function Config.reset(self)
self._lastConfigTraceback = nil
self._currentConfig = defaultConfig
end

return Config
86 changes: 86 additions & 0 deletions lib/Config.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
return function()
local Config = require(script.Parent.Config)

it("should accept valid configuration", function()
local config = Config.new()

expect(config.getValue("elementTracing")).to.equal(false)

config.set({
elementTracing = true,
})

expect(config.getValue("elementTracing")).to.equal(true)
end)

it("should reject invalid configuration keys", function()
local config = Config.new()

local badKey = "garblegoop"

local ok, err = pcall(function()
config.set({
[badKey] = true,
})
end)

expect(ok).to.equal(false)

-- The error should mention our bad key somewhere.
expect(err:find(badKey)).to.be.ok()
end)

it("should reject invalid configuration values", function()
local config = Config.new()

local goodKey = "elementTracing"
local badValue = "Hello there!"

local ok, err = pcall(function()
config.set({
[goodKey] = badValue,
})
end)

expect(ok).to.equal(false)

-- The error should mention both our key and value
expect(err:find(goodKey)).to.be.ok()
expect(err:find(badValue)).to.be.ok()
end)

it("should prevent setting configuration more than once", function()
local config = Config.new()

-- We're going to use the name of this function to see if the traceback
-- was correct.
local function setEmptyConfig()
config.set({})
end

setEmptyConfig()

local ok, err = pcall(setEmptyConfig)

expect(ok).to.equal(false)

-- The error should mention the stack trace with the original set call.
expect(err:find("setEmptyConfig")).to.be.ok()
end)

it("should reset to default values after invoking reset()", function()
local config = Config.new()

expect(config.getValue("elementTracing")).to.equal(false)

config.set({
elementTracing = true,
})

expect(config.getValue("elementTracing")).to.equal(true)

config.reset()

expect(config.getValue("elementTracing")).to.equal(false)
end)
end
13 changes: 2 additions & 11 deletions lib/Core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
]]

local Symbol = require(script.Parent.Symbol)
local GlobalConfig = require(script.Parent.GlobalConfig)

local Core = {}

Expand All @@ -22,16 +23,6 @@ Core.Portal = Symbol.named("Portal")
-- Marker used to specify that the value is nothing, because nil cannot be stored in tables.
Core.None = Symbol.named("None")

Core._DEBUG_ENABLED = false

function Core.DEBUG_ENABLE()
if Core._DEBUG_ENABLED then
error("Can only call Roact.DEBUG_ENABLE once!", 2)
end

Core._DEBUG_ENABLED = true
end

--[[
Utility to retrieve one child out the children passed to a component.

Expand Down Expand Up @@ -85,7 +76,7 @@ function Core.createElement(elementType, props, children)
props = props,
}

if Core._DEBUG_ENABLED then
if GlobalConfig.getValue("elementTracing") then
element.source = ("\n%s\n"):format(debug.traceback())
end

Expand Down
40 changes: 0 additions & 40 deletions lib/Debug.lua

This file was deleted.

7 changes: 7 additions & 0 deletions lib/GlobalConfig.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
--[[
Exposes a single instance of a configuration as Roact's GlobalConfig.
]]

local Config = require(script.Parent.Config)

return Config.new()
10 changes: 10 additions & 0 deletions lib/GlobalConfig.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
return function()
local GlobalConfig = require(script.Parent.GlobalConfig)

it("should have the correct methods", function()
expect(GlobalConfig).to.be.ok()
expect(GlobalConfig.set).to.be.ok()
expect(GlobalConfig.getValue).to.be.ok()
expect(GlobalConfig.reset).to.be.ok()
end)
end
2 changes: 1 addition & 1 deletion lib/Reconciler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ local Symbol = require(script.Parent.Symbol)

local isInstanceHandle = Symbol.named("isInstanceHandle")

local DEFAULT_SOURCE = "\n\t<Use Roact.DEBUG_ENABLE() to enable detailed tracebacks>\n"
local DEFAULT_SOURCE = "\n\t<Use Roact.setGlobalConfig with the 'elementTracing' key to enable detailed tracebacks>\n"

local function isPortal(element)
if type(element) ~= "table" then
Expand Down
8 changes: 3 additions & 5 deletions lib/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

local Component = require(script.Component)
local Core = require(script.Core)
local Debug = require(script.Debug)
local Event = require(script.Event)
local GlobalConfig = require(script.GlobalConfig)
local PureComponent = require(script.PureComponent)
local Reconciler = require(script.Reconciler)

Expand Down Expand Up @@ -39,11 +39,9 @@ apply(Roact, {
Event = Event,
})

-- Apply unstable modules in a special place.
apply(Roact, {
Unstable = {
Debug = Debug,
}
setGlobalConfig = GlobalConfig.set,
getGlobalConfigValue = GlobalConfig.getValue,
})

return Roact
2 changes: 2 additions & 0 deletions lib/init.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ return function()
expect(Roact.reify).to.be.a("function")
expect(Roact.reconcile).to.be.a("function")
expect(Roact.oneChild).to.be.a("function")
expect(Roact.setGlobalConfig).to.be.a("function")
expect(Roact.getGlobalConfigValue).to.be.a("function")

-- Objects
expect(Roact.Component).to.be.ok()
Expand Down