From 7ffe5d1385cc06717a0acde48059e08327ab097c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 20 Oct 2014 14:52:12 -0700 Subject: [PATCH 1/4] Add support for scoped defaults in config schemas --- spec/config-spec.coffee | 15 +++++++++++++++ src/config.coffee | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 01143115f..84561a93d 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1032,6 +1032,21 @@ describe "Config", -> expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe true expect(atom.config.get('foo.bar.arr')).toEqual ['two', 'three'] + describe "when scoped settings are used", -> + beforeEach -> + schema = + type: 'string' + default: 'ok' + scopes: + '.source.js': + default: 'omg' + atom.config.setSchema('foo.bar.str', schema) + + it 'it respects the scoped defaults', -> + expect(atom.config.get('foo.bar.str')).toBe 'ok' + expect(atom.config.get(['.source.js'], 'foo.bar.str')).toBe 'omg' + expect(atom.config.get(['.source.coffee'], 'foo.bar.str')).toBe 'ok' + describe "scoped settings", -> describe ".get(scopeDescriptor, keyPath)", -> it "returns the property with the most specific scope selector", -> diff --git a/src/config.coffee b/src/config.coffee index 3cc6be6b0..b5048ddbd 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -679,6 +679,7 @@ class Config _.extend rootSchema, schema @setDefaults(keyPath, @extractDefaultsFromSchema(schema)) + @setScopedDefaultsFromSchema(keyPath, schema) load: -> @initializeConfigDirectory() @@ -833,6 +834,32 @@ class Config catch e console.warn("'#{keyPath}' could not set the default. Attempted default: #{JSON.stringify(defaults)}; Schema: #{JSON.stringify(@getSchema(keyPath))}") + # `schema` will look something like this + # + # ```coffee + # type: 'string' + # default: 'ok' + # scopes: + # '.source.js': + # default: 'omg' + # ``` + setScopedDefaultsFromSchema: (keyPath, schema) -> + if schema.scopes? and isPlainObject(schema.scopes) + scopedDefaults = {} + for scope, scopeSchema of schema.scopes + continue unless scopeSchema.hasOwnProperty('default') + scopedDefaults[scope] = {} + _.setValueForKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default) + @scopedSettingsStore.addProperties('schema-default', scopedDefaults) + + if schema.type is 'object' and schema.properties? and isPlainObject(schema.properties) + keys = if keyPath? then keyPath.split('.') else [] + for key, childValue of schema.properties + continue unless schema.properties.hasOwnProperty(key) + @setScopedDefaultsFromSchema(keys.concat([key]).join('.'), childValue) + + return + extractDefaultsFromSchema: (schema) -> if schema.default? schema.default From c40dd16466bf01d8aeb9b00c26b264af62490588 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 20 Oct 2014 16:57:46 -0700 Subject: [PATCH 2/4] Upgrade to underscore-plus 1.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7da4bfb8f..6e0599076 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "temp": "0.7.0", "text-buffer": "^3.2.9", "theorist": "^1.0.2", - "underscore-plus": "^1.5.1", + "underscore-plus": "^1.6.0", "vm-compatibility-layer": "0.1.0" }, "packageDependencies": { From 2b148b772026ab1575a0eada70bc860734dd9c23 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 20 Oct 2014 16:58:57 -0700 Subject: [PATCH 3/4] Accept escaped dots in config settings keys Fixes #3898 --- spec/config-spec.coffee | 6 +++++- src/config.coffee | 25 ++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 84561a93d..cdd071091 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -122,6 +122,11 @@ describe "Config", -> atom.config.set('.source.coffee', 'foo.bar.baz', 55) expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe false + describe ".setDefaults(keyPath)", -> + it "sets a default when the setting's key contains an escaped dot", -> + atom.config.setDefaults("foo", 'a\\.b': 1, b: 2) + expect(atom.config.get("foo")).toEqual 'a\\.b': 1, b: 2 + describe ".toggle(keyPath)", -> it "negates the boolean value of the current key path value", -> atom.config.set('foo.a', 1) @@ -213,7 +218,6 @@ describe "Config", -> baz: 42 omg: 'omg' - describe ".pushAtKeyPath(keyPath, value)", -> it "pushes the given value to the array at the key path and updates observers", -> atom.config.set("foo.bar.baz", ["a"]) diff --git a/src/config.coffee b/src/config.coffee index b5048ddbd..2fd30d924 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -590,7 +590,7 @@ class Config # Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. # Returns `null` when the keyPath has no schema specified. getSchema: (keyPath) -> - keys = keyPath.split('.') + keys = splitKeyPath(keyPath) schema = @schema for key in keys break unless schema? @@ -670,7 +670,7 @@ class Config rootSchema = @schema if keyPath - for key in keyPath.split('.') + for key in splitKeyPath(keyPath) rootSchema.type = 'object' rootSchema.properties ?= {} properties = rootSchema.properties @@ -755,7 +755,7 @@ class Config unsetUnspecifiedValues = (keyPath, value) => if isPlainObject(value) - keys = if keyPath? then keyPath.split('.') else [] + keys = splitKeyPath(keyPath) for key, childValue of value continue unless value.hasOwnProperty(key) unsetUnspecifiedValues(keys.concat([key]).join('.'), childValue) @@ -768,7 +768,7 @@ class Config setRecursive: (keyPath, value) -> if isPlainObject(value) - keys = if keyPath? then keyPath.split('.') else [] + keys = splitKeyPath(keyPath) for key, childValue of value continue unless value.hasOwnProperty(key) @setRecursive(keys.concat([key]).join('.'), childValue) @@ -811,8 +811,8 @@ class Config isSubKeyPath: (keyPath, subKeyPath) -> return false unless keyPath? and subKeyPath? - pathSubTokens = subKeyPath.split('.') - pathTokens = keyPath.split('.').slice(0, pathSubTokens.length) + pathSubTokens = splitKeyPath(subKeyPath) + pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length) _.isEqual(pathTokens, pathSubTokens) setRawDefault: (keyPath, value) -> @@ -823,7 +823,7 @@ class Config setDefaults: (keyPath, defaults) -> if defaults? and isPlainObject(defaults) - keys = if keyPath? then keyPath.split('.') else [] + keys = splitKeyPath(keyPath) for key, childValue of defaults continue unless defaults.hasOwnProperty(key) @setDefaults(keys.concat([key]).join('.'), childValue) @@ -1044,3 +1044,14 @@ Config.addSchemaEnforcers isPlainObject = (value) -> _.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value) + +splitKeyPath = (keyPath) -> + return [] unless keyPath? + startIndex = 0 + keyPathArray = [] + for char, i in keyPath + if char is '.' and (i is 0 or keyPath[i-1] != '\\') + keyPathArray.push keyPath.substring(startIndex, i) + startIndex = i + 1 + keyPathArray.push keyPath.substr(startIndex, keyPath.length) + keyPathArray From ffd3990a6ba1e9a7e5bb2458829e24ea6629cedb Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 21 Oct 2014 11:57:01 -0700 Subject: [PATCH 4/4] Split with the new method --- src/config.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index 2fd30d924..bf03a36f7 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -853,7 +853,7 @@ class Config @scopedSettingsStore.addProperties('schema-default', scopedDefaults) if schema.type is 'object' and schema.properties? and isPlainObject(schema.properties) - keys = if keyPath? then keyPath.split('.') else [] + keys = splitKeyPath(keyPath) for key, childValue of schema.properties continue unless schema.properties.hasOwnProperty(key) @setScopedDefaultsFromSchema(keys.concat([key]).join('.'), childValue)