howl.config
before_each ->
config.reset!
app.editor = nil
reset clears all set values, but keeps the definitions
config.define name: 'var', description: 'test'
config.define name: 'var2', description: 'test'
config.set 'var', 'set'
config.set_default 'var2', 'set-default'
config.reset!
assert.is_not_nil config.definitions['var']
assert.is_nil config.get 'var'
assert.is_nil config.get 'var2'
global variables can be set and get directly on config
config.define name: 'direct', description: 'test', default: 123
assert.equal config.direct, 123
config.direct = 'bar'
assert.equal config.direct, 'bar'
assert.equal config.get('direct'), 'bar'
define(options)
raises an error if name is missing
assert.raises 'name', -> config.define {}
raises an error if the description option is missing
assert.raises 'description', -> config.define name: 'foo'
.definitions
is a table of the current definitions, keyed by name
var = name: 'foo', description: 'foo variable'
config.define var
assert.equal type(config.definitions), 'table'
assert.same var, config.definitions.foo
writing directly to it raises an error
assert.has_error -> config.definitions.frob = 'crazy'
set(name, value)
sets <name> globally to <value>
var = name: 'foo', description: 'foo variable'
config.define var
config.set 'foo', 2
assert.equal config.get('foo'), 2
an error is raised if <name> is not defined
assert.raises 'Undefined', -> config.set 'que', 'si'
setting a value of nil clears the value
var = name: 'foo', description: 'foo variable'
config.define var
config.set 'foo', 'bar'
config.set 'foo', nil
assert.is_nil config.foo
get(name, scope, layer)
before_each -> config.define name: 'var', description: 'test variable'
returns the global value of <name>
config.set 'var', 'hello'
assert.equal config.get('var'), 'hello'
(scopes)
get() returns value set for specific scope
config.set 'var', 'global-value'
config.set 'var', 'scope-value', 'scope1'
assert.equal 'scope-value', config.get 'var', 'scope1'
get() looks up the value set() in parent scopes if necessary
config.set 'var', 'global-value'
config.set 'var', 'top-value', 'root'
config.set 'var', 'scope-value', 'root/scope1'
assert.equal 'scope-value', config.get 'var', 'root/scope1'
assert.equal 'top-value', config.get 'var', 'root/scope2'
assert.equal 'global-value', config.get 'var', 'scope3'
(layers)
before_each ->
config.define name: 'var', description: 'test variable'
config.define_layer 'layer1'
config.define_layer 'layer2'
set() errors for undefined layer
assert.raises 'Unknown', -> config.set 'var', 'value', '', 'no-such-layer'
get() looks up specified layer before default
config.set 'var', 'layer1-value', '', 'layer1'
config.set 'var', 'default-value', ''
assert.equal 'layer1-value', config.get 'var', '', 'layer1'
assert.equal 'default-value', config.get 'var', '', 'layer2'
layers are looked up at each scope
config.set 'var', 'layer1-top', '', 'layer1'
config.set 'var', 'layer2-top', '', 'layer2'
config.set 'var', 'default-top', ''
config.set 'var', 'layer1-scope', 'scope1', 'layer1'
assert.equal 'layer1-scope', config.get 'var', 'scope1', 'layer1'
assert.equal 'layer2-top', config.get 'var', 'scope1', 'layer2'
(when a default is provided in the definition)
before_each -> config.define name: 'with_default', description: 'test', default: 123
the default value is returned if no value has been set
assert.equal config.get('with_default'), 123
if a value has been set it takes precedence
config.set 'with_default', 'foo'
assert.equal config.get('with_default'), 'foo'
(when a default has been set by set_default)
before_each ->
config.define name: 'var', description: 'test variable', default: 'def-default'
config.define_layer 'layer1'
config.define_layer 'layer2'
set_default value takes precedence over definition default
config.set_default 'var', 'set-default', 'layer1'
assert.equal 'set-default', config.get 'var', 'scope1', 'layer1'
assert.equal 'def-default', config.get 'var', 'scope1'
set_default for a layer takes precedence over global config value
config.set_default 'var', 'set-default', 'layer1'
config.var = 'global-value'
assert.equal 'set-default', config.get 'var', 'scope1', 'layer1'
assert.equal 'global-value', config.get 'var', 'scope1'
set_default value is not persisted
with_tmpdir (dir) ->
config.set_default 'var', 'set-default', 'layer1'
config.save_config dir
config.load_config true, dir
assert.equal 'set-default', config.get 'var', 'scope1', 'layer1'
assert.equal 'def-default', config.get 'var', 'scope1'
(when a validate function is provided)
is called with the value to be set whenever the variable is set
validate = spy.new -> true
config.define name: 'validated', description: 'test', :validate
config.set 'validated', 'my_value'
assert.spy(validate).was_called_with 'my_value'
an error is raised if the function returns false for to-be set value
config.define name: 'validated', description: 'test', validate: -> false
assert.error -> config.set 'validated', 'foo'
config.define name: 'validated', description: 'test', validate: -> nil
assert.no_error -> config.set 'validated', 'foo'
config.define name: 'validated', description: 'test', validate: -> true
assert.no_error -> config.set 'validated', 'foo'
an error is not raised if the function returns truish for to-be set value
config.define name: 'validated', description: 'test', validate: -> true
config.set 'validated', 'foo'
assert.equal config.get('validated'), 'foo'
config.define name: 'validated', description: 'test', validate: -> 2
config.set 'validated', 'foo2'
assert.equal config.get('validated'), 'foo2'
the function is not called when clearing a value by setting it to nil
validate = Spy!
config.define name: 'validated', description: 'test', :validate
config.set 'validated', nil
assert.is_false validate.called
(when a convert function is provided)
is called with the value to be set and the return value is used instead
config.define name: 'converted', description: 'test', convert: -> 'wanted'
config.set 'converted', 'requested'
assert.equal config.converted, 'wanted'
(when options is provided)
before_each ->
config.define
name: 'with_options'
description: 'test'
options: { 'one', 'two' }
an error is raised if the to-be set value is not a valid option
assert.raises 'option', -> config.set 'with_options', 'three'
an error is not raised if the to-be set value is a valid option
config.set 'with_options', 'one'
options can be a function returning a table
config.define name: 'with_options_func', description: 'test', options: ->
{ 'one', 'two' }
assert.raises 'option', -> config.set 'with_options_func', 'three'
config.set 'with_options_func', 'one'
options can be a table of tables containg values and descriptions
options = {
{ 'one', 'description for one' }
{ 'two', 'description for two' }
}
config.define name: 'with_options_desc', description: 'test', :options
assert.raises 'option', -> config.set 'with_options_desc', 'three'
config.set 'with_options_desc', 'one'
(when scope is provided)
raises an error if it is not "local" or "global"
assert.raises 'scope', -> config.define name: 'bla', description: 'foo', scope: 'blarg'
(.. with local scope for a variable)
an error is raised when trying to set the global value of the variable
config.define name: 'local', description: 'test', scope: 'local'
assert.error -> config.set 'local', 'foo'
(when type_of is provided)
raises an error if the type is not recognized
assert.raises 'type', -> config.define name: 'bla', description: 'foo', type_of: 'blarg'
(.. and is "boolean")
def = nil
before_each ->
config.define name: 'bool', description: 'foo', type_of: 'boolean'
def = config.definitions.bool
options are {true, false}
assert.same def.options, { true, false }
convert handles boolean types and "true" and "false"
assert.equal def.convert(true), true
assert.equal def.convert(false), false
assert.equal def.convert('true'), true
assert.equal def.convert('false'), false
assert.equal def.convert('blargh'), 'blargh'
converts to boolean upon assignment
config.bool = 'false'
assert.equal config.bool, false
(.. and is "number")
def = nil
before_each ->
config.define name: 'number', description: 'foo', type_of: 'number'
def = config.definitions.number
convert handles numbers and string numbers
assert.equal def.convert(1), 1
assert.equal def.convert('1'), 1
assert.equal def.convert(0.5), 0.5
assert.equal def.convert('0.5'), 0.5
assert.equal def.convert('blargh'), 'blargh'
validate returns true for numbers only
assert.is_true def.validate 1
assert.is_true def.validate 1.2
assert.is_false def.validate '1'
assert.is_false def.validate 'blargh'
converts to number upon assignment
config.number = '1'
assert.equal config.number, 1
(.. and is "positive_number")
def = nil
before_each ->
config.define name: 'positive_number', description: 'foo', type_of: 'positive_number'
def = config.definitions.positive_number
convert handles numbers and string numbers
assert.equal def.convert(1), 1
assert.equal def.convert('1'), 1
assert.equal def.convert(0.5), 0.5
assert.equal def.convert('0.5'), 0.5
assert.equal def.convert('blargh'), 'blargh'
validate returns true for positive numbers only
assert.is_true def.validate 1
assert.is_true def.validate 1.2
assert.is_false def.validate -1
assert.is_false def.validate -1.2
assert.is_false def.validate '1'
assert.is_false def.validate 'blargh'
converts to number upon assignment
config.number = '1'
assert.equal config.number, 1
(.. and is "string_list")
def = nil
before_each ->
config.define name: 'string_list', description: 'foo', type_of: 'string_list'
def = config.definitions.string_list
validate returns true for table values
assert.is_true def.validate {}
assert.is_false def.validate '1'
assert.is_false def.validate 23
convert
leaves string tables alone
orig = { 'hi', 'there' }
assert.same orig, def.convert orig
converts values in other tables as necessary
orig = { 1, 2 }
assert.same { '1', '2', }, def.convert orig
converts simple values into a table
assert.same { '1' }, def.convert '1'
assert.same { '1' }, def.convert 1
converts a blank string into an empty table
assert.same {}, def.convert ''
assert.same {}, def.convert ' '
converts a comma separated string into a list of values
assert.same { '1', '2' }, def.convert '1,2'
assert.same { '1', '2' }, def.convert ' 1 , 2 '
(watching)
before_each -> config.define name: 'trigger', description: 'watchable'
watch(name, function) register a watcher for <name>
assert.not_error -> config.watch 'foo', -> true
set invokes watchers with <name>, <value> and false
callback = Spy!
config.watch 'trigger', callback
config.set 'trigger', 'value'
assert.same callback.called_with, { 'trigger', 'value', false }
define(..) invokes watchers with <name>, <default-value> and false
callback = spy.new ->
config.watch 'undefined', callback
config.define name: 'undefined', description: 'springs into life', default: 123
assert.spy(callback).was_called_with 'undefined', 123, false
(.. when a callback raises an error)
before_each -> config.watch 'trigger', -> error 'oh noes'
other callbacks are still invoked
callback = Spy!
config.watch 'trigger', callback
config.set 'trigger', 'value'
assert.is_true callback.called
an error is logged
config.set 'trigger', 'value'
assert.match log.last_error.message, 'watcher'
proxy
config.define
name: 'my_var'
description: 'base'
type_of: 'number'
local proxy_inner, proxy_outer
before_each ->
config.my_var = 123
proxy_inner = config.proxy '/outer/inner'
proxy_outer = config.proxy '/outer'
returns a table with access to all previously defined variables
assert.equal 123, proxy_outer.my_var
assert.equal 123, proxy_inner.my_var
changing a variable changes it locally only
proxy_inner.my_var = 321
assert.equal 321, proxy_inner.my_var
assert.equal 123, proxy_outer.my_var
assert.equal 123, config.my_var
assignments are still validated and converted as usual
assert.has_error -> proxy_inner.my_var = 'not a number'
proxy_inner.my_var = '111'
assert.equal 111, proxy_inner.my_var
an error is raised if trying to set a variable with global scope
config.define name: 'global', description: 'global', scope: 'global'
assert.has_error -> proxy_inner.global = 'illegal'
an error is raised if the variable is not defined
assert.raises 'Undefined', -> proxy_inner.que = 'si'
setting a value to nil clears the value
proxy_inner.my_var = 666
proxy_inner.my_var = nil
assert.equal 123, proxy_inner.my_var
setting a variable via a proxy invokes watchers with <name>, <value> and true
callback = spy.new ->
config.watch 'my_var', callback
proxy_inner.my_var = 333
assert.spy(callback).was_called_with 'my_var', 333, true
(chaining)
resolution automatically walks up scope hierarchy
proxy_inner.my_var = 1
proxy_outer.my_var = 2
assert.same 1, proxy_inner.my_var
assert.same 2, proxy_outer.my_var
proxy_inner.my_var = nil
assert.same 2, proxy_inner.my_var
proxy_outer.my_var = nil
assert.same 123, proxy_inner.my_var
(.. when layers are specified)
local proxy_inner_layered, proxy_outer_layered, proxy_inner_mixed, proxy_inner_sub
before_each ->
config.define_layer 'layer:one'
config.define_layer 'layer:sub', parent: 'layer:one'
proxy_inner_layered = config.proxy '/outer/inner', 'layer:one'
proxy_outer_layered = config.proxy '/outer', 'layer:one'
proxy_inner_mixed = config.proxy '/outer/inner', 'default', 'layer:one'
proxy_inner_sub = config.proxy '/outer/inner', 'layer:sub'
layer is checked before default values at each scope
proxy_inner_layered.my_var = 4
proxy_inner.my_var = 3
proxy_outer_layered.my_var = 2
proxy_outer.my_var = 1
assert.same 4, proxy_inner_layered.my_var
proxy_inner_layered.my_var = nil
assert.same 3, proxy_inner_layered.my_var
proxy_inner.my_var = nil
assert.same 2, proxy_inner_layered.my_var
read for a layer auto delegates up to parent layer
proxy_inner_layered.my_var = 5
assert.same 5, proxy_inner_sub.my_var
read layer can be different from write layer
proxy_inner_layered.my_var = 4
proxy_inner_mixed.my_var = 3
assert.same 4, proxy_inner_mixed.my_var
(replace())
before_each ->
config.define_layer 'layer1'
config.define_layer 'layer2'
config.define name: 'name1', description: 'description'
config.define name: 'name2', description: 'description'
clobbers new scope and deep copies all values from scope to new scope
config.set 'name1', 'value1', 'here', 'layer1'
config.set 'name2', 'value2', 'here', 'layer2'
assert.is_nil config.get 'name1', 'there', 'layer1'
assert.is_nil config.get 'name2', 'there', 'layer2'
config.replace 'here', 'there'
assert.same 'value1', config.get 'name1', 'there', 'layer1'
assert.same 'value2', config.get 'name2', 'there', 'layer2'
config.set 'name1', 'value1-new', 'here', 'layer1'
assert.same 'value1', config.get 'name1', 'there', 'layer1'
(merge())
before_each ->
config.define_layer 'layer1'
config.define_layer 'layer2'
config.define name: 'name1', description: 'description'
config.define name: 'name2', description: 'description'
config.define name: 'name3', description: 'description'
deep copies values from scope to new scope, preserves other values in old scope
config.set 'name1', 'value1', 'here', 'layer1'
config.set 'name2', 'value2', 'here', 'layer2'
config.set 'name1', 'there-value1', 'there', 'layer1'
config.set 'name3', 'there-value3', 'there', 'layer2'
assert.same 'there-value1', config.get 'name1', 'there', 'layer1'
assert.same nil, config.get 'name2', 'there', 'layer2'
config.merge 'here', 'there'
assert.same 'value1', config.get 'name1', 'there', 'layer1'
assert.same 'value2', config.get 'name2', 'there', 'layer2'
assert.same 'there-value3', config.get 'name3', 'there', 'layer2'
(delete())
before_each ->
config.define name: 'name1', description: 'description'
deletes all values at specified scope
config.set 'name1', 'val1', 'scope1'
config.set 'name1', 'top-val'
config.delete 'scope1'
assert.equal 'top-val', config.get 'name1', 'scope1'
errors when trying to delete global scope
assert.raises 'global', -> config.delete ''
(persistence)
before_each ->
config.define name: 'name1', description: 'description'
config.define name: 'name2', description: 'description'
config.define_layer 'layer1'
save_config() saves and load_config() loads the saved config
with_tmpdir (dir) ->
config.set 'name1', 'value1'
config.set 'name2', 'value2'
config.save_config dir
config.set 'name1', nil
assert.same nil, config.get 'name1'
config.load_config true, dir
assert.same 'value1', config.get 'name1'
assert.same 'value2', config.get 'name2'
non global scopes are not persisted
with_tmpdir (dir) ->
config.set 'name1', 'value1', 'scope1'
config.save_config dir
config.set 'name1', 'value2', 'scope1'
config.load_config true, dir
assert.same nil, config.get 'name1', 'scope1'
does not save values if persist_config is false
with_tmpdir (dir) ->
config.set 'name1', 'value1'
config.set 'name2', 'value2'
config.set 'persist_config', false
config.save_config dir
config.set 'name1', nil
config.set 'name2', nil
assert.same nil, config.get 'name1'
config.load_config true, dir
assert.same nil, config.get 'name1'
assert.same nil, config.get 'name1', 'scope1'
saves persist_config value
with_tmpdir (dir) ->
config.set 'name1', 'value1'
config.set 'persist_config', false
config.save_config dir
config.set 'name1', nil
assert.same nil, config.get 'name1'
config.load_config true, dir
assert.same false, config.get 'persist_config'
does not save buffer scopes
with_tmpdir (dir) ->
config.set 'name1', 'value1-global'
config.set 'name1', 'value2-buffer', 'buffer/123'
config.save_config dir
config.load_config true, dir
assert.same 'value1-global', config.get 'name1', 'buffer/123'
(for_file)
before_each ->
config.define name: 'name1', description: ''
config.define name: 'name2', description: ''
config.define name: 'name3', description: ''
config.define_layer 'layer1'
config.define_layer 'layer2'
returns a proxy that inherits from global
with_tmpdir (dir) ->
config.set 'name1', 'value1'
assert.same 'value1', config.for_file(dir .. 'abc').name1
returns the same config for same file path
with_tmpdir (dir) ->
fileconfig = config.for_file(dir .. 'abc')
fileconfig.name1 = 'file-value1'
fileconfig2 = config.for_file(dir.path .. '/abc')
assert.same 'file-value1', fileconfig2.name1
constructs scopes that inherit from ancestor file scopes
with_tmpdir (dir) ->
with config.for_file(dir)
.name1 = 'dir-value1'
.name2 = 'dir-value2'
with config.for_file(dir .. 'abc')
.name1 = 'file-value1'
.name3= 'file-value3'
assert.same 'file-value1', config.for_file(dir .. 'abc').name1
assert.same 'dir-value2', config.for_file(dir .. 'abc').name2
assert.same nil, config.for_file(dir).name33
assert.same nil, config.name3
(for_layer)
creates another proxy for specified layer at same scope
with_tmpdir (dir) ->
with config.for_file(dir).for_layer('layer1')
.name1 = 'dir-layer1-value1'
with config.for_file(dir)
.name1 = 'dir-value1'
with config.for_file(dir..'abc').for_layer('layer1')
.name1 = 'file-layer1-value1'
assert.same 'file-layer1-value1', config.for_file(dir..'abc').for_layer('layer1').name1
assert.same 'dir-value1', config.for_file(dir..'abc').name1