diff --git a/src/config.coffee b/src/config.coffee deleted file mode 100644 index 84e726700..000000000 --- a/src/config.coffee +++ /dev/null @@ -1,1353 +0,0 @@ -_ = require 'underscore-plus' -fs = require 'fs-plus' -{Emitter} = require 'event-kit' -CSON = require 'season' -path = require 'path' -async = require 'async' -{watchPath} = require './path-watcher' -{ - getValueAtKeyPath, setValueAtKeyPath, deleteValueAtKeyPath, - pushKeyPath, splitKeyPath, -} = require 'key-path-helpers' - -Color = require './color' -ScopedPropertyStore = require 'scoped-property-store' -ScopeDescriptor = require './scope-descriptor' - -# Essential: Used to access all of Atom's configuration details. -# -# An instance of this class is always available as the `atom.config` global. -# -# ## Getting and setting config settings. -# -# ```coffee -# # Note that with no value set, ::get returns the setting's default value. -# atom.config.get('my-package.myKey') # -> 'defaultValue' -# -# atom.config.set('my-package.myKey', 'value') -# atom.config.get('my-package.myKey') # -> 'value' -# ``` -# -# You may want to watch for changes. Use {::observe} to catch changes to the setting. -# -# ```coffee -# atom.config.set('my-package.myKey', 'value') -# atom.config.observe 'my-package.myKey', (newValue) -> -# # `observe` calls immediately and every time the value is changed -# console.log 'My configuration changed:', newValue -# ``` -# -# If you want a notification only when the value changes, use {::onDidChange}. -# -# ```coffee -# atom.config.onDidChange 'my-package.myKey', ({newValue, oldValue}) -> -# console.log 'My configuration changed:', newValue, oldValue -# ``` -# -# ### Value Coercion -# -# Config settings each have a type specified by way of a -# [schema](json-schema.org). For example we might an integer setting that only -# allows integers greater than `0`: -# -# ```coffee -# # When no value has been set, `::get` returns the setting's default value -# atom.config.get('my-package.anInt') # -> 12 -# -# # The string will be coerced to the integer 123 -# atom.config.set('my-package.anInt', '123') -# atom.config.get('my-package.anInt') # -> 123 -# -# # The string will be coerced to an integer, but it must be greater than 0, so is set to 1 -# atom.config.set('my-package.anInt', '-20') -# atom.config.get('my-package.anInt') # -> 1 -# ``` -# -# ## Defining settings for your package -# -# Define a schema under a `config` key in your package main. -# -# ```coffee -# module.exports = -# # Your config schema -# config: -# someInt: -# type: 'integer' -# default: 23 -# minimum: 1 -# -# activate: (state) -> # ... -# # ... -# ``` -# -# See [package docs](http://flight-manual.atom.io/hacking-atom/sections/package-word-count/) for -# more info. -# -# ## Config Schemas -# -# We use [json schema](http://json-schema.org) which allows you to define your value's -# default, the type it should be, etc. A simple example: -# -# ```coffee -# # We want to provide an `enableThing`, and a `thingVolume` -# config: -# enableThing: -# type: 'boolean' -# default: false -# thingVolume: -# type: 'integer' -# default: 5 -# minimum: 1 -# maximum: 11 -# ``` -# -# The type keyword allows for type coercion and validation. If a `thingVolume` is -# set to a string `'10'`, it will be coerced into an integer. -# -# ```coffee -# atom.config.set('my-package.thingVolume', '10') -# atom.config.get('my-package.thingVolume') # -> 10 -# -# # It respects the min / max -# atom.config.set('my-package.thingVolume', '400') -# atom.config.get('my-package.thingVolume') # -> 11 -# -# # If it cannot be coerced, the value will not be set -# atom.config.set('my-package.thingVolume', 'cats') -# atom.config.get('my-package.thingVolume') # -> 11 -# ``` -# -# ### Supported Types -# -# The `type` keyword can be a string with any one of the following. You can also -# chain them by specifying multiple in an an array. For example -# -# ```coffee -# config: -# someSetting: -# type: ['boolean', 'integer'] -# default: 5 -# -# # Then -# atom.config.set('my-package.someSetting', 'true') -# atom.config.get('my-package.someSetting') # -> true -# -# atom.config.set('my-package.someSetting', '12') -# atom.config.get('my-package.someSetting') # -> 12 -# ``` -# -# #### string -# -# Values must be a string. -# -# ```coffee -# config: -# someSetting: -# type: 'string' -# default: 'hello' -# ``` -# -# #### integer -# -# Values will be coerced into integer. Supports the (optional) `minimum` and -# `maximum` keys. -# -# ```coffee -# config: -# someSetting: -# type: 'integer' -# default: 5 -# minimum: 1 -# maximum: 11 -# ``` -# -# #### number -# -# Values will be coerced into a number, including real numbers. Supports the -# (optional) `minimum` and `maximum` keys. -# -# ```coffee -# config: -# someSetting: -# type: 'number' -# default: 5.3 -# minimum: 1.5 -# maximum: 11.5 -# ``` -# -# #### boolean -# -# Values will be coerced into a Boolean. `'true'` and `'false'` will be coerced into -# a boolean. Numbers, arrays, objects, and anything else will not be coerced. -# -# ```coffee -# config: -# someSetting: -# type: 'boolean' -# default: false -# ``` -# -# #### array -# -# Value must be an Array. The types of the values can be specified by a -# subschema in the `items` key. -# -# ```coffee -# config: -# someSetting: -# type: 'array' -# default: [1, 2, 3] -# items: -# type: 'integer' -# minimum: 1.5 -# maximum: 11.5 -# ``` -# -# #### color -# -# Values will be coerced into a {Color} with `red`, `green`, `blue`, and `alpha` -# properties that all have numeric values. `red`, `green`, `blue` will be in -# the range 0 to 255 and `value` will be in the range 0 to 1. Values can be any -# valid CSS color format such as `#abc`, `#abcdef`, `white`, -# `rgb(50, 100, 150)`, and `rgba(25, 75, 125, .75)`. -# -# ```coffee -# config: -# someSetting: -# type: 'color' -# default: 'white' -# ``` -# -# #### object / Grouping other types -# -# A config setting with the type `object` allows grouping a set of config -# settings. The group will be visually separated and has its own group headline. -# The sub options must be listed under a `properties` key. -# -# ```coffee -# config: -# someSetting: -# type: 'object' -# properties: -# myChildIntOption: -# type: 'integer' -# minimum: 1.5 -# maximum: 11.5 -# ``` -# -# ### Other Supported Keys -# -# #### enum -# -# All types support an `enum` key, which lets you specify all the values the -# setting can take. `enum` may be an array of allowed values (of the specified -# type), or an array of objects with `value` and `description` properties, where -# the `value` is an allowed value, and the `description` is a descriptive string -# used in the settings view. -# -# In this example, the setting must be one of the 4 integers: -# -# ```coffee -# config: -# someSetting: -# type: 'integer' -# default: 4 -# enum: [2, 4, 6, 8] -# ``` -# -# In this example, the setting must be either 'foo' or 'bar', which are -# presented using the provided descriptions in the settings pane: -# -# ```coffee -# config: -# someSetting: -# type: 'string' -# default: 'foo' -# enum: [ -# {value: 'foo', description: 'Foo mode. You want this.'} -# {value: 'bar', description: 'Bar mode. Nobody wants that!'} -# ] -# ``` -# -# Usage: -# -# ```coffee -# atom.config.set('my-package.someSetting', '2') -# atom.config.get('my-package.someSetting') # -> 2 -# -# # will not set values outside of the enum values -# atom.config.set('my-package.someSetting', '3') -# atom.config.get('my-package.someSetting') # -> 2 -# -# # If it cannot be coerced, the value will not be set -# atom.config.set('my-package.someSetting', '4') -# atom.config.get('my-package.someSetting') # -> 4 -# ``` -# -# #### title and description -# -# The settings view will use the `title` and `description` keys to display your -# config setting in a readable way. By default the settings view humanizes your -# config key, so `someSetting` becomes `Some Setting`. In some cases, this is -# confusing for users, and a more descriptive title is useful. -# -# Descriptions will be displayed below the title in the settings view. -# -# For a group of config settings the humanized key or the title and the -# description are used for the group headline. -# -# ```coffee -# config: -# someSetting: -# title: 'Setting Magnitude' -# description: 'This will affect the blah and the other blah' -# type: 'integer' -# default: 4 -# ``` -# -# __Note__: You should strive to be so clear in your naming of the setting that -# you do not need to specify a title or description! -# -# Descriptions allow a subset of -# [Markdown formatting](https://help.github.com/articles/github-flavored-markdown/). -# Specifically, you may use the following in configuration setting descriptions: -# -# * **bold** - `**bold**` -# * *italics* - `*italics*` -# * [links](https://atom.io) - `[links](https://atom.io)` -# * `code spans` - `\`code spans\`` -# * line breaks - `line breaks
` -# * ~~strikethrough~~ - `~~strikethrough~~` -# -# #### order -# -# The settings view orders your settings alphabetically. You can override this -# ordering with the order key. -# -# ```coffee -# config: -# zSetting: -# type: 'integer' -# default: 4 -# order: 1 -# aSetting: -# type: 'integer' -# default: 4 -# order: 2 -# ``` -# -# ## Manipulating values outside your configuration schema -# -# It is possible to manipulate(`get`, `set`, `observe` etc) values that do not -# appear in your configuration schema. For example, if the config schema of the -# package 'some-package' is -# -# ```coffee -# config: -# someSetting: -# type: 'boolean' -# default: false -# ``` -# -# You can still do the following -# -# ```coffee -# let otherSetting = atom.config.get('some-package.otherSetting') -# atom.config.set('some-package.stillAnotherSetting', otherSetting * 5) -# ``` -# -# In other words, if a function asks for a `key-path`, that path doesn't have to -# be described in the config schema for the package or any package. However, as -# highlighted in the best practices section, you are advised against doing the -# above. -# -# ## Best practices -# -# * Don't depend on (or write to) configuration keys outside of your keypath. -# -module.exports = -class Config - @schemaEnforcers = {} - - @addSchemaEnforcer: (typeName, enforcerFunction) -> - @schemaEnforcers[typeName] ?= [] - @schemaEnforcers[typeName].push(enforcerFunction) - - @addSchemaEnforcers: (filters) -> - for typeName, functions of filters - for name, enforcerFunction of functions - @addSchemaEnforcer(typeName, enforcerFunction) - return - - @executeSchemaEnforcers: (keyPath, value, schema) -> - error = null - types = schema.type - types = [types] unless Array.isArray(types) - for type in types - try - enforcerFunctions = @schemaEnforcers[type].concat(@schemaEnforcers['*']) - for enforcer in enforcerFunctions - # At some point in one's life, one must call upon an enforcer. - value = enforcer.call(this, keyPath, value, schema) - error = null - break - catch e - error = e - - throw error if error? - value - - # Created during initialization, available as `atom.config` - constructor: ({@notificationManager, @enablePersistence}={}) -> - @clear() - - initialize: ({@configDirPath, @resourcePath, projectHomeSchema}) -> - if @enablePersistence? - @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) - @configFilePath ?= path.join(@configDirPath, 'config.cson') - - @schema.properties.core.properties.projectHome = projectHomeSchema - @defaultSettings.core.projectHome = projectHomeSchema.default - - clear: -> - @emitter = new Emitter - @schema = - type: 'object' - properties: {} - @defaultSettings = {} - @settings = {} - @scopedSettingsStore = new ScopedPropertyStore - - @settingsLoaded = false - @savePending = false - @configFileHasErrors = false - @transactDepth = 0 - @pendingOperations = [] - @legacyScopeAliases = {} - - @requestLoad = _.debounce => - @loadUserConfig() - , 100 - - debouncedSave = _.debounce => - @savePending = false - @save() - , 100 - @requestSave = => - @savePending = true - debouncedSave() - - shouldNotAccessFileSystem: -> not @enablePersistence - - ### - Section: Config Subscription - ### - - # Essential: Add a listener for changes to a given key path. This is different - # than {::onDidChange} in that it will immediately call your callback with the - # current value of the config entry. - # - # ### Examples - # - # You might want to be notified when the themes change. We'll watch - # `core.themes` for changes - # - # ```coffee - # atom.config.observe 'core.themes', (value) -> - # # do stuff with value - # ``` - # - # * `keyPath` {String} name of the key to observe - # * `options` (optional) {Object} - # * `scope` (optional) {ScopeDescriptor} describing a path from - # the root of the syntax tree to a token. Get one by calling - # {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples. - # See [the scopes docs](http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/) - # for more information. - # * `callback` {Function} to call when the value of the key changes. - # * `value` the new value of the key - # - # Returns a {Disposable} with the following keys on which you can call - # `.dispose()` to unsubscribe. - observe: -> - if arguments.length is 2 - [keyPath, callback] = arguments - else if arguments.length is 3 and (_.isString(arguments[0]) and _.isObject(arguments[1])) - [keyPath, options, callback] = arguments - scopeDescriptor = options.scope - else - console.error 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details' - return - - if scopeDescriptor? - @observeScopedKeyPath(scopeDescriptor, keyPath, callback) - else - @observeKeyPath(keyPath, options ? {}, callback) - - # Essential: Add a listener for changes to a given key path. If `keyPath` is - # not specified, your callback will be called on changes to any key. - # - # * `keyPath` (optional) {String} name of the key to observe. Must be - # specified if `scopeDescriptor` is specified. - # * `options` (optional) {Object} - # * `scope` (optional) {ScopeDescriptor} describing a path from - # the root of the syntax tree to a token. Get one by calling - # {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples. - # See [the scopes docs](http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/) - # for more information. - # * `callback` {Function} to call when the value of the key changes. - # * `event` {Object} - # * `newValue` the new value of the key - # * `oldValue` the prior value of the key. - # - # Returns a {Disposable} with the following keys on which you can call - # `.dispose()` to unsubscribe. - onDidChange: -> - if arguments.length is 1 - [callback] = arguments - else if arguments.length is 2 - [keyPath, callback] = arguments - else - [keyPath, options, callback] = arguments - scopeDescriptor = options.scope - - if scopeDescriptor? - @onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback) - else - @onDidChangeKeyPath(keyPath, callback) - - ### - Section: Managing Settings - ### - - # Essential: Retrieves the setting for the given key. - # - # ### Examples - # - # You might want to know what themes are enabled, so check `core.themes` - # - # ```coffee - # atom.config.get('core.themes') - # ``` - # - # With scope descriptors you can get settings within a specific editor - # scope. For example, you might want to know `editor.tabLength` for ruby - # files. - # - # ```coffee - # atom.config.get('editor.tabLength', scope: ['source.ruby']) # => 2 - # ``` - # - # This setting in ruby files might be different than the global tabLength setting - # - # ```coffee - # atom.config.get('editor.tabLength') # => 4 - # atom.config.get('editor.tabLength', scope: ['source.ruby']) # => 2 - # ``` - # - # You can get the language scope descriptor via - # {TextEditor::getRootScopeDescriptor}. This will get the setting specifically - # for the editor's language. - # - # ```coffee - # atom.config.get('editor.tabLength', scope: @editor.getRootScopeDescriptor()) # => 2 - # ``` - # - # Additionally, you can get the setting at the specific cursor position. - # - # ```coffee - # scopeDescriptor = @editor.getLastCursor().getScopeDescriptor() - # atom.config.get('editor.tabLength', scope: scopeDescriptor) # => 2 - # ``` - # - # * `keyPath` The {String} name of the key to retrieve. - # * `options` (optional) {Object} - # * `sources` (optional) {Array} of {String} source names. If provided, only - # values that were associated with these sources during {::set} will be used. - # * `excludeSources` (optional) {Array} of {String} source names. If provided, - # values that were associated with these sources during {::set} will not - # be used. - # * `scope` (optional) {ScopeDescriptor} describing a path from - # the root of the syntax tree to a token. Get one by calling - # {editor.getLastCursor().getScopeDescriptor()} - # See [the scopes docs](http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/) - # for more information. - # - # Returns the value from Atom's default settings, the user's configuration - # file in the type specified by the configuration schema. - get: -> - if arguments.length > 1 - if typeof arguments[0] is 'string' or not arguments[0]? - [keyPath, options] = arguments - {scope} = options - else - [keyPath] = arguments - - if scope? - value = @getRawScopedValue(scope, keyPath, options) - value ? @getRawValue(keyPath, options) - else - @getRawValue(keyPath, options) - - # Extended: Get all of the values for the given key-path, along with their - # associated scope selector. - # - # * `keyPath` The {String} name of the key to retrieve - # * `options` (optional) {Object} see the `options` argument to {::get} - # - # Returns an {Array} of {Object}s with the following keys: - # * `scopeDescriptor` The {ScopeDescriptor} with which the value is associated - # * `value` The value for the key-path - getAll: (keyPath, options) -> - {scope} = options if options? - - if scope? - scopeDescriptor = ScopeDescriptor.fromObject(scope) - result = @scopedSettingsStore.getAll( - scopeDescriptor.getScopeChain(), - keyPath, - options - ) - if legacyScopeDescriptor = @getLegacyScopeDescriptor(scopeDescriptor) - result.push(@scopedSettingsStore.getAll( - legacyScopeDescriptor.getScopeChain(), - keyPath, - options - )...) - else - result = [] - - if globalValue = @getRawValue(keyPath, options) - result.push(scopeSelector: '*', value: globalValue) - - result - - # Essential: Sets the value for a configuration setting. - # - # This value is stored in Atom's internal configuration file. - # - # ### Examples - # - # You might want to change the themes programmatically: - # - # ```coffee - # atom.config.set('core.themes', ['atom-light-ui', 'atom-light-syntax']) - # ``` - # - # You can also set scoped settings. For example, you might want change the - # `editor.tabLength` only for ruby files. - # - # ```coffee - # atom.config.get('editor.tabLength') # => 4 - # atom.config.get('editor.tabLength', scope: ['source.ruby']) # => 4 - # atom.config.get('editor.tabLength', scope: ['source.js']) # => 4 - # - # # Set ruby to 2 - # atom.config.set('editor.tabLength', 2, scopeSelector: '.source.ruby') # => true - # - # # Notice it's only set to 2 in the case of ruby - # atom.config.get('editor.tabLength') # => 4 - # atom.config.get('editor.tabLength', scope: ['source.ruby']) # => 2 - # atom.config.get('editor.tabLength', scope: ['source.js']) # => 4 - # ``` - # - # * `keyPath` The {String} name of the key. - # * `value` The value of the setting. Passing `undefined` will revert the - # setting to the default value. - # * `options` (optional) {Object} - # * `scopeSelector` (optional) {String}. eg. '.source.ruby' - # See [the scopes docs](http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/) - # for more information. - # * `source` (optional) {String} The name of a file with which the setting - # is associated. Defaults to the user's config file. - # - # Returns a {Boolean} - # * `true` if the value was set. - # * `false` if the value was not able to be coerced to the type specified in the setting's schema. - set: -> - [keyPath, value, options] = arguments - - unless @settingsLoaded - @pendingOperations.push => @set.call(this, keyPath, value, options) - - scopeSelector = options?.scopeSelector - source = options?.source - shouldSave = options?.save ? true - - if source and not scopeSelector - throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!") - - source ?= @getUserConfigPath() - - unless value is undefined - try - value = @makeValueConformToSchema(keyPath, value) - catch e - return false - - if scopeSelector? - @setRawScopedValue(keyPath, value, source, scopeSelector) - else - @setRawValue(keyPath, value) - - if source is @getUserConfigPath() and shouldSave and not @configFileHasErrors and @settingsLoaded - @requestSave() - true - - # Essential: Restore the setting at `keyPath` to its default value. - # - # * `keyPath` The {String} name of the key. - # * `options` (optional) {Object} - # * `scopeSelector` (optional) {String}. See {::set} - # * `source` (optional) {String}. See {::set} - unset: (keyPath, options) -> - unless @settingsLoaded - @pendingOperations.push => @unset.call(this, keyPath, options) - - {scopeSelector, source} = options ? {} - source ?= @getUserConfigPath() - - if scopeSelector? - if keyPath? - settings = @scopedSettingsStore.propertiesForSourceAndSelector(source, scopeSelector) - if getValueAtKeyPath(settings, keyPath)? - @scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) - setValueAtKeyPath(settings, keyPath, undefined) - settings = withoutEmptyObjects(settings) - @set(null, settings, {scopeSelector, source, priority: @priorityForSource(source)}) if settings? - if source is @getUserConfigPath() and not @configFileHasErrors and @settingsLoaded - @requestSave() - else - @scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) - @emitChangeEvent() - else - for scopeSelector of @scopedSettingsStore.propertiesForSource(source) - @unset(keyPath, {scopeSelector, source}) - if keyPath? and source is @getUserConfigPath() - @set(keyPath, getValueAtKeyPath(@defaultSettings, keyPath)) - - # Extended: Get an {Array} of all of the `source` {String}s with which - # settings have been added via {::set}. - getSources: -> - _.uniq(_.pluck(@scopedSettingsStore.propertySets, 'source')).sort() - - # Extended: Retrieve the schema for a specific key path. The schema will tell - # you what type the keyPath expects, and other metadata about the config - # option. - # - # * `keyPath` The {String} name of the key. - # - # Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. - # Returns `null` when the keyPath has no schema specified, but is accessible - # from the root schema. - getSchema: (keyPath) -> - keys = splitKeyPath(keyPath) - schema = @schema - for key in keys - if schema.type is 'object' - childSchema = schema.properties?[key] - unless childSchema? - if isPlainObject(schema.additionalProperties) - childSchema = schema.additionalProperties - else if schema.additionalProperties is false - return null - else - return {type: 'any'} - else - return null - schema = childSchema - schema - - # Extended: Get the {String} path to the config file being used. - getUserConfigPath: -> - @configFilePath - - # Extended: Suppress calls to handler functions registered with {::onDidChange} - # and {::observe} for the duration of `callback`. After `callback` executes, - # handlers will be called once if the value for their key-path has changed. - # - # * `callback` {Function} to execute while suppressing calls to handlers. - transact: (callback) -> - @beginTransaction() - try - callback() - finally - @endTransaction() - - addLegacyScopeAlias: (languageId, legacyScopeName) -> - @legacyScopeAliases[languageId] = legacyScopeName - - removeLegacyScopeAlias: (languageId) -> - delete @legacyScopeAliases[languageId] - - ### - Section: Internal methods used by core - ### - - # Private: Suppress calls to handler functions registered with {::onDidChange} - # and {::observe} for the duration of the {Promise} returned by `callback`. - # After the {Promise} is either resolved or rejected, handlers will be called - # once if the value for their key-path has changed. - # - # * `callback` {Function} that returns a {Promise}, which will be executed - # while suppressing calls to handlers. - # - # Returns a {Promise} that is either resolved or rejected according to the - # `{Promise}` returned by `callback`. If `callback` throws an error, a - # rejected {Promise} will be returned instead. - transactAsync: (callback) -> - @beginTransaction() - try - endTransaction = (fn) => (args...) => - @endTransaction() - fn(args...) - result = callback() - new Promise (resolve, reject) -> - result.then(endTransaction(resolve)).catch(endTransaction(reject)) - catch error - @endTransaction() - Promise.reject(error) - - beginTransaction: -> - @transactDepth++ - - endTransaction: -> - @transactDepth-- - @emitChangeEvent() - - pushAtKeyPath: (keyPath, value) -> - arrayValue = @get(keyPath) ? [] - result = arrayValue.push(value) - @set(keyPath, arrayValue) - result - - unshiftAtKeyPath: (keyPath, value) -> - arrayValue = @get(keyPath) ? [] - result = arrayValue.unshift(value) - @set(keyPath, arrayValue) - result - - removeAtKeyPath: (keyPath, value) -> - arrayValue = @get(keyPath) ? [] - result = _.remove(arrayValue, value) - @set(keyPath, arrayValue) - result - - setSchema: (keyPath, schema) -> - unless isPlainObject(schema) - throw new Error("Error loading schema for #{keyPath}: schemas can only be objects!") - - unless typeof schema.type? - throw new Error("Error loading schema for #{keyPath}: schema objects must have a type attribute") - - rootSchema = @schema - if keyPath - for key in splitKeyPath(keyPath) - rootSchema.type = 'object' - rootSchema.properties ?= {} - properties = rootSchema.properties - properties[key] ?= {} - rootSchema = properties[key] - - Object.assign rootSchema, schema - @transact => - @setDefaults(keyPath, @extractDefaultsFromSchema(schema)) - @setScopedDefaultsFromSchema(keyPath, schema) - @resetSettingsForSchemaChange() - - load: -> - @initializeConfigDirectory() - @loadUserConfig() - @observeUserConfig() - - ### - Section: Private methods managing the user's config file - ### - - initializeConfigDirectory: (done) -> - return if fs.existsSync(@configDirPath) or @shouldNotAccessFileSystem() - - fs.makeTreeSync(@configDirPath) - - queue = async.queue ({sourcePath, destinationPath}, callback) -> - fs.copy(sourcePath, destinationPath, callback) - queue.drain = done - - templateConfigDirPath = fs.resolve(@resourcePath, 'dot-atom') - onConfigDirFile = (sourcePath) => - relativePath = sourcePath.substring(templateConfigDirPath.length + 1) - destinationPath = path.join(@configDirPath, relativePath) - queue.push({sourcePath, destinationPath}) - fs.traverseTree(templateConfigDirPath, onConfigDirFile, ((path) -> true), (->)) - - loadUserConfig: -> - return if @shouldNotAccessFileSystem() - return if @savePending - - try - unless fs.existsSync(@configFilePath) - fs.makeTreeSync(path.dirname(@configFilePath)) - CSON.writeFileSync(@configFilePath, {}, {flag: 'wx'}) # fails if file exists - catch error - if error.code isnt 'EEXIST' - @configFileHasErrors = true - @notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack) - return - - try - userConfig = CSON.readFileSync(@configFilePath) - userConfig = {} if userConfig is null - - unless isPlainObject(userConfig) - throw new Error("`#{path.basename(@configFilePath)}` must contain valid JSON or CSON") - - @resetUserSettings(userConfig) - @configFileHasErrors = false - catch error - @configFileHasErrors = true - message = "Failed to load `#{path.basename(@configFilePath)}`" - - detail = if error.location? - # stack is the output from CSON in this case - error.stack - else - # message will be EACCES permission denied, et al - error.message - - @notifyFailure(message, detail) - - observeUserConfig: -> - return if @shouldNotAccessFileSystem() - - try - @watchSubscriptionPromise ?= watchPath @configFilePath, {}, (events) => - for {action} in events - if action in ['created', 'modified', 'renamed'] and @watchSubscriptionPromise? - @requestLoad() - catch error - @notifyFailure """ - Unable to watch path: `#{path.basename(@configFilePath)}`. Make sure you have permissions to - `#{@configFilePath}`. On linux there are currently problems with watch - sizes. See [this document][watches] for more info. - [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path - """ - - @watchSubscriptionPromise - - unobserveUserConfig: -> - @watchSubscriptionPromise?.then (watcher) -> watcher?.dispose() - @watchSubscriptionPromise = null - - notifyFailure: (errorMessage, detail) -> - @notificationManager?.addError(errorMessage, {detail, dismissable: true}) - - save: -> - return if @shouldNotAccessFileSystem() - - allSettings = {'*': @settings} - allSettings = Object.assign allSettings, @scopedSettingsStore.propertiesForSource(@getUserConfigPath()) - allSettings = sortObject(allSettings) - try - CSON.writeFileSync(@configFilePath, allSettings) - catch error - message = "Failed to save `#{path.basename(@configFilePath)}`" - detail = error.message - @notifyFailure(message, detail) - - ### - Section: Private methods managing global settings - ### - - resetUserSettings: (newSettings) -> - if newSettings.global? - newSettings['*'] = newSettings.global - delete newSettings.global - - if newSettings['*']? - scopedSettings = newSettings - newSettings = newSettings['*'] - delete scopedSettings['*'] - @resetUserScopedSettings(scopedSettings) - - @transact => - @settings = {} - @settingsLoaded = true - @set(key, value, save: false) for key, value of newSettings - if @pendingOperations.length - op() for op in @pendingOperations - @pendingOperations = [] - - getRawValue: (keyPath, options) -> - unless options?.excludeSources?.indexOf(@getUserConfigPath()) >= 0 - value = getValueAtKeyPath(@settings, keyPath) - unless options?.sources?.length > 0 - defaultValue = getValueAtKeyPath(@defaultSettings, keyPath) - - if value? - value = @deepClone(value) - @deepDefaults(value, defaultValue) if isPlainObject(value) and isPlainObject(defaultValue) - else - value = @deepClone(defaultValue) - - value - - setRawValue: (keyPath, value) -> - defaultValue = getValueAtKeyPath(@defaultSettings, keyPath) - if _.isEqual(defaultValue, value) - if keyPath? - deleteValueAtKeyPath(@settings, keyPath) - else - @settings = null - else - if keyPath? - setValueAtKeyPath(@settings, keyPath, value) - else - @settings = value - @emitChangeEvent() - - observeKeyPath: (keyPath, options, callback) -> - callback(@get(keyPath)) - @onDidChangeKeyPath keyPath, (event) -> callback(event.newValue) - - onDidChangeKeyPath: (keyPath, callback) -> - oldValue = @get(keyPath) - @emitter.on 'did-change', => - newValue = @get(keyPath) - unless _.isEqual(oldValue, newValue) - event = {oldValue, newValue} - oldValue = newValue - callback(event) - - isSubKeyPath: (keyPath, subKeyPath) -> - return false unless keyPath? and subKeyPath? - pathSubTokens = splitKeyPath(subKeyPath) - pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length) - _.isEqual(pathTokens, pathSubTokens) - - setRawDefault: (keyPath, value) -> - setValueAtKeyPath(@defaultSettings, keyPath, value) - @emitChangeEvent() - - setDefaults: (keyPath, defaults) -> - if defaults? and isPlainObject(defaults) - keys = splitKeyPath(keyPath) - @transact => - for key, childValue of defaults - continue unless defaults.hasOwnProperty(key) - @setDefaults(keys.concat([key]).join('.'), childValue) - else - try - defaults = @makeValueConformToSchema(keyPath, defaults) - @setRawDefault(keyPath, defaults) - catch e - console.warn("'#{keyPath}' could not set the default. Attempted default: #{JSON.stringify(defaults)}; Schema: #{JSON.stringify(@getSchema(keyPath))}") - return - - deepClone: (object) -> - if object instanceof Color - object.clone() - else if _.isArray(object) - object.map (value) => @deepClone(value) - else if isPlainObject(object) - _.mapObject object, (key, value) => [key, @deepClone(value)] - else - object - - deepDefaults: (target) -> - result = target - i = 0 - while ++i < arguments.length - object = arguments[i] - if isPlainObject(result) and isPlainObject(object) - for key in Object.keys(object) - result[key] = @deepDefaults(result[key], object[key]) - else - if not result? - result = @deepClone(object) - result - - # `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] = {} - setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default) - @scopedSettingsStore.addProperties('schema-default', scopedDefaults) - - if schema.type is 'object' and schema.properties? and isPlainObject(schema.properties) - keys = splitKeyPath(keyPath) - 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 - else if schema.type is 'object' and schema.properties? and isPlainObject(schema.properties) - defaults = {} - properties = schema.properties or {} - defaults[key] = @extractDefaultsFromSchema(value) for key, value of properties - defaults - - makeValueConformToSchema: (keyPath, value, options) -> - if options?.suppressException - try - @makeValueConformToSchema(keyPath, value) - catch e - undefined - else - unless (schema = @getSchema(keyPath))? - throw new Error("Illegal key path #{keyPath}") if schema is false - @constructor.executeSchemaEnforcers(keyPath, value, schema) - - # When the schema is changed / added, there may be values set in the config - # that do not conform to the schema. This will reset make them conform. - resetSettingsForSchemaChange: (source=@getUserConfigPath()) -> - @transact => - @settings = @makeValueConformToSchema(null, @settings, suppressException: true) - selectorsAndSettings = @scopedSettingsStore.propertiesForSource(source) - @scopedSettingsStore.removePropertiesForSource(source) - for scopeSelector, settings of selectorsAndSettings - settings = @makeValueConformToSchema(null, settings, suppressException: true) - @setRawScopedValue(null, settings, source, scopeSelector) - return - - ### - Section: Private Scoped Settings - ### - - priorityForSource: (source) -> - if source is @getUserConfigPath() - 1000 - else - 0 - - emitChangeEvent: -> - @emitter.emit 'did-change' unless @transactDepth > 0 - - resetUserScopedSettings: (newScopedSettings) -> - source = @getUserConfigPath() - priority = @priorityForSource(source) - @scopedSettingsStore.removePropertiesForSource(source) - - for scopeSelector, settings of newScopedSettings - settings = @makeValueConformToSchema(null, settings, suppressException: true) - validatedSettings = {} - validatedSettings[scopeSelector] = withoutEmptyObjects(settings) - @scopedSettingsStore.addProperties(source, validatedSettings, {priority}) if validatedSettings[scopeSelector]? - - @emitChangeEvent() - - setRawScopedValue: (keyPath, value, source, selector, options) -> - if keyPath? - newValue = {} - setValueAtKeyPath(newValue, keyPath, value) - value = newValue - - settingsBySelector = {} - settingsBySelector[selector] = value - @scopedSettingsStore.addProperties(source, settingsBySelector, priority: @priorityForSource(source)) - @emitChangeEvent() - - getRawScopedValue: (scopeDescriptor, keyPath, options) -> - scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor) - result = @scopedSettingsStore.getPropertyValue( - scopeDescriptor.getScopeChain(), - keyPath, - options - ) - - if result? - result - else if legacyScopeDescriptor = @getLegacyScopeDescriptor(scopeDescriptor) - @scopedSettingsStore.getPropertyValue( - legacyScopeDescriptor.getScopeChain(), - keyPath, - options - ) - - observeScopedKeyPath: (scope, keyPath, callback) -> - callback(@get(keyPath, {scope})) - @onDidChangeScopedKeyPath scope, keyPath, (event) -> callback(event.newValue) - - onDidChangeScopedKeyPath: (scope, keyPath, callback) -> - oldValue = @get(keyPath, {scope}) - @emitter.on 'did-change', => - newValue = @get(keyPath, {scope}) - unless _.isEqual(oldValue, newValue) - event = {oldValue, newValue} - oldValue = newValue - callback(event) - - getLegacyScopeDescriptor: (scopeDescriptor) -> - legacyAlias = @legacyScopeAliases[scopeDescriptor.scopes[0]] - if legacyAlias - scopes = scopeDescriptor.scopes.slice() - scopes[0] = legacyAlias - new ScopeDescriptor({scopes}) - -# Base schema enforcers. These will coerce raw input into the specified type, -# and will throw an error when the value cannot be coerced. Throwing the error -# will indicate that the value should not be set. -# -# Enforcers are run from most specific to least. For a schema with type -# `integer`, all the enforcers for the `integer` type will be run first, in -# order of specification. Then the `*` enforcers will be run, in order of -# specification. -Config.addSchemaEnforcers - 'any': - coerce: (keyPath, value, schema) -> - value - - 'integer': - coerce: (keyPath, value, schema) -> - value = parseInt(value) - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} cannot be coerced into an int") if isNaN(value) or not isFinite(value) - value - - 'number': - coerce: (keyPath, value, schema) -> - value = parseFloat(value) - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} cannot be coerced into a number") if isNaN(value) or not isFinite(value) - value - - 'boolean': - coerce: (keyPath, value, schema) -> - switch typeof value - when 'string' - if value.toLowerCase() is 'true' - true - else if value.toLowerCase() is 'false' - false - else - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be a boolean or the string 'true' or 'false'") - when 'boolean' - value - else - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be a boolean or the string 'true' or 'false'") - - 'string': - validate: (keyPath, value, schema) -> - unless typeof value is 'string' - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be a string") - value - - validateMaximumLength: (keyPath, value, schema) -> - if typeof schema.maximumLength is 'number' and value.length > schema.maximumLength - value.slice(0, schema.maximumLength) - else - value - - 'null': - # null sort of isnt supported. It will just unset in this case - coerce: (keyPath, value, schema) -> - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be null") unless value in [undefined, null] - value - - 'object': - coerce: (keyPath, value, schema) -> - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be an object") unless isPlainObject(value) - return value unless schema.properties? - - defaultChildSchema = null - allowsAdditionalProperties = true - if isPlainObject(schema.additionalProperties) - defaultChildSchema = schema.additionalProperties - if schema.additionalProperties is false - allowsAdditionalProperties = false - - newValue = {} - for prop, propValue of value - childSchema = schema.properties[prop] ? defaultChildSchema - if childSchema? - try - newValue[prop] = @executeSchemaEnforcers(pushKeyPath(keyPath, prop), propValue, childSchema) - catch error - console.warn "Error setting item in object: #{error.message}" - else if allowsAdditionalProperties - # Just pass through un-schema'd values - newValue[prop] = propValue - else - console.warn "Illegal object key: #{keyPath}.#{prop}" - - newValue - - 'array': - coerce: (keyPath, value, schema) -> - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be an array") unless Array.isArray(value) - itemSchema = schema.items - if itemSchema? - newValue = [] - for item in value - try - newValue.push @executeSchemaEnforcers(keyPath, item, itemSchema) - catch error - console.warn "Error setting item in array: #{error.message}" - newValue - else - value - - 'color': - coerce: (keyPath, value, schema) -> - color = Color.parse(value) - unless color? - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} cannot be coerced into a color") - color - - '*': - coerceMinimumAndMaximum: (keyPath, value, schema) -> - return value unless typeof value is 'number' - if schema.minimum? and typeof schema.minimum is 'number' - value = Math.max(value, schema.minimum) - if schema.maximum? and typeof schema.maximum is 'number' - value = Math.min(value, schema.maximum) - value - - validateEnum: (keyPath, value, schema) -> - possibleValues = schema.enum - - if Array.isArray(possibleValues) - possibleValues = possibleValues.map (value) -> - if value.hasOwnProperty('value') then value.value else value - - return value unless possibleValues? and Array.isArray(possibleValues) and possibleValues.length - - for possibleValue in possibleValues - # Using `isEqual` for possibility of placing enums on array and object schemas - return value if _.isEqual(possibleValue, value) - - throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} is not one of #{JSON.stringify(possibleValues)}") - -isPlainObject = (value) -> - _.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value) and not (value instanceof Color) - -sortObject = (value) -> - return value unless isPlainObject(value) - result = {} - for key in Object.keys(value).sort() - result[key] = sortObject(value[key]) - result - -withoutEmptyObjects = (object) -> - resultObject = undefined - if isPlainObject(object) - for key, value of object - newValue = withoutEmptyObjects(value) - if newValue? - resultObject ?= {} - resultObject[key] = newValue - else - resultObject = object - resultObject diff --git a/src/config.js b/src/config.js index a6c2288e7..abb0f8c8c 100644 --- a/src/config.js +++ b/src/config.js @@ -1,3 +1,5 @@ +/** @babel */ + /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from @@ -9,22 +11,22 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let Config; -const _ = require('underscore-plus'); -const fs = require('fs-plus'); -const {Emitter} = require('event-kit'); -const CSON = require('season'); -const path = require('path'); -const async = require('async'); -const {watchPath} = require('./path-watcher'); + +const _ = require('underscore-plus') +const fs = require('fs-plus') +const {Emitter} = require('event-kit') +const CSON = require('season') +const path = require('path') +const async = require('async') +const {watchPath} = require('./path-watcher') const { getValueAtKeyPath, setValueAtKeyPath, deleteValueAtKeyPath, - pushKeyPath, splitKeyPath, -} = require('key-path-helpers'); + pushKeyPath, splitKeyPath +} = require('key-path-helpers') -const Color = require('./color'); -const ScopedPropertyStore = require('scoped-property-store'); -const ScopeDescriptor = require('./scope-descriptor'); +const Color = require('./color') +const ScopedPropertyStore = require('scoped-property-store') +const ScopeDescriptor = require('./scope-descriptor') // Essential: Used to access all of Atom's configuration details. // @@ -377,105 +379,101 @@ const ScopeDescriptor = require('./scope-descriptor'); // // * Don't depend on (or write to) configuration keys outside of your keypath. // -module.exports = -(Config = (function() { - Config = class Config { - static initClass() { - this.schemaEnforcers = {}; - } +const schemaEnforcers = {} - static addSchemaEnforcer(typeName, enforcerFunction) { - if (this.schemaEnforcers[typeName] == null) { this.schemaEnforcers[typeName] = []; } - return this.schemaEnforcers[typeName].push(enforcerFunction); - } +export default class Config { + static addSchemaEnforcer (typeName, enforcerFunction) { + if (schemaEnforcers[typeName] == null) { schemaEnforcers[typeName] = [] } + return schemaEnforcers[typeName].push(enforcerFunction) + } - static addSchemaEnforcers(filters) { - for (let typeName in filters) { - const functions = filters[typeName]; - for (let name in functions) { - const enforcerFunction = functions[name]; - this.addSchemaEnforcer(typeName, enforcerFunction); - } + static addSchemaEnforcers (filters) { + for (let typeName in filters) { + const functions = filters[typeName] + for (let name in functions) { + const enforcerFunction = functions[name] + this.addSchemaEnforcer(typeName, enforcerFunction) } } + } - static executeSchemaEnforcers(keyPath, value, schema) { - let error = null; - let types = schema.type; - if (!Array.isArray(types)) { types = [types]; } - for (let type of Array.from(types)) { - try { - const enforcerFunctions = this.schemaEnforcers[type].concat(this.schemaEnforcers['*']); - for (let enforcer of Array.from(enforcerFunctions)) { + static executeSchemaEnforcers (keyPath, value, schema) { + let error = null + let types = schema.type + if (!Array.isArray(types)) { types = [types] } + for (let type of Array.from(types)) { + try { + const enforcerFunctions = schemaEnforcers[type].concat(schemaEnforcers['*']) + for (let enforcer of Array.from(enforcerFunctions)) { // At some point in one's life, one must call upon an enforcer. - value = enforcer.call(this, keyPath, value, schema); - } - error = null; - break; - } catch (e) { - error = e; + value = enforcer.call(this, keyPath, value, schema) } + error = null + break + } catch (e) { + error = e } - - if (error != null) { throw error; } - return value; } + if (error != null) { throw error } + return value + } + // Created during initialization, available as `atom.config` - constructor(param) { - if (param == null) { param = {}; } - const {notificationManager, enablePersistence} = param; - this.notificationManager = notificationManager; - this.enablePersistence = enablePersistence; - this.clear(); + constructor (param) { + if (param == null) { param = {} } + const {notificationManager, enablePersistence} = param + this.notificationManager = notificationManager + this.enablePersistence = enablePersistence + this.clear() + } + + initialize ({configDirPath, resourcePath, projectHomeSchema}) { + this.configDirPath = configDirPath + this.resourcePath = resourcePath + if (this.enablePersistence != null) { + this.configFilePath = fs.resolve(this.configDirPath, 'config', ['json', 'cson']) + if (this.configFilePath == null) { this.configFilePath = path.join(this.configDirPath, 'config.cson') } } - initialize({configDirPath, resourcePath, projectHomeSchema}) { - this.configDirPath = configDirPath; - this.resourcePath = resourcePath; - if (this.enablePersistence != null) { - this.configFilePath = fs.resolve(this.configDirPath, 'config', ['json', 'cson']); - if (this.configFilePath == null) { this.configFilePath = path.join(this.configDirPath, 'config.cson'); } - } + this.schema.properties.core.properties.projectHome = projectHomeSchema + this.defaultSettings.core.projectHome = projectHomeSchema.default + } - this.schema.properties.core.properties.projectHome = projectHomeSchema; - return this.defaultSettings.core.projectHome = projectHomeSchema.default; + clear () { + this.emitter = new Emitter() + this.schema = { + type: 'object', + properties: {} } + this.defaultSettings = {} + this.settings = {} + this.scopedSettingsStore = new ScopedPropertyStore() - clear() { - this.emitter = new Emitter; - this.schema = { - type: 'object', - properties: {} - }; - this.defaultSettings = {}; - this.settings = {}; - this.scopedSettingsStore = new ScopedPropertyStore; + this.settingsLoaded = false + this.savePending = false + this.configFileHasErrors = false + this.transactDepth = 0 + this.pendingOperations = [] + this.legacyScopeAliases = {} - this.settingsLoaded = false; - this.savePending = false; - this.configFileHasErrors = false; - this.transactDepth = 0; - this.pendingOperations = []; - this.legacyScopeAliases = {}; - - this.requestLoad = _.debounce(() => { - return this.loadUserConfig(); - } - , 100); - - const debouncedSave = _.debounce(() => { - this.savePending = false; - return this.save(); - } - , 100); - return this.requestSave = () => { - this.savePending = true; - return debouncedSave(); - }; + this.requestLoad = _.debounce(() => { + return this.loadUserConfig() } + , 100) - shouldNotAccessFileSystem() { return !this.enablePersistence; } + const debouncedSave = _.debounce(() => { + this.savePending = false + return this.save() + } + , 100) + this.requestSave = () => { + this.savePending = true + debouncedSave() + } + } + + shouldNotAccessFileSystem () { return !this.enablePersistence } /* Section: Config Subscription @@ -507,25 +505,25 @@ module.exports = // // Returns a {Disposable} with the following keys on which you can call // `.dispose()` to unsubscribe. - observe() { - let callback, keyPath, options, scopeDescriptor; - if (arguments.length === 2) { - [keyPath, callback] = Array.from(arguments); - } else if ((arguments.length === 3) && (_.isString(arguments[0]) && _.isObject(arguments[1]))) { - [keyPath, options, callback] = Array.from(arguments); - scopeDescriptor = options.scope; - } else { - console.error('An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details'); - return; - } - - if (scopeDescriptor != null) { - return this.observeScopedKeyPath(scopeDescriptor, keyPath, callback); - } else { - return this.observeKeyPath(keyPath, options != null ? options : {}, callback); - } + observe () { + let callback, keyPath, options, scopeDescriptor + if (arguments.length === 2) { + [keyPath, callback] = Array.from(arguments) + } else if ((arguments.length === 3) && (_.isString(arguments[0]) && _.isObject(arguments[1]))) { + [keyPath, options, callback] = Array.from(arguments) + scopeDescriptor = options.scope + } else { + console.error('An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details') + return } + if (scopeDescriptor != null) { + return this.observeScopedKeyPath(scopeDescriptor, keyPath, callback) + } else { + return this.observeKeyPath(keyPath, options != null ? options : {}, callback) + } + } + // Essential: Add a listener for changes to a given key path. If `keyPath` is // not specified, your callback will be called on changes to any key. // @@ -544,25 +542,25 @@ module.exports = // // Returns a {Disposable} with the following keys on which you can call // `.dispose()` to unsubscribe. - onDidChange() { - let callback, keyPath, scopeDescriptor; - if (arguments.length === 1) { - [callback] = Array.from(arguments); - } else if (arguments.length === 2) { - [keyPath, callback] = Array.from(arguments); - } else { - let options; - [keyPath, options, callback] = Array.from(arguments); - scopeDescriptor = options.scope; - } - - if (scopeDescriptor != null) { - return this.onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback); - } else { - return this.onDidChangeKeyPath(keyPath, callback); - } + onDidChange () { + let callback, keyPath, scopeDescriptor + if (arguments.length === 1) { + [callback] = Array.from(arguments) + } else if (arguments.length === 2) { + [keyPath, callback] = Array.from(arguments) + } else { + let options; + [keyPath, options, callback] = Array.from(arguments) + scopeDescriptor = options.scope } + if (scopeDescriptor != null) { + return this.onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback) + } else { + return this.onDidChangeKeyPath(keyPath, callback) + } + } + /* Section: Managing Settings */ @@ -622,25 +620,25 @@ module.exports = // // Returns the value from Atom's default settings, the user's configuration // file in the type specified by the configuration schema. - get() { - let keyPath, options, scope; - if (arguments.length > 1) { - if ((typeof arguments[0] === 'string') || (arguments[0] == null)) { - [keyPath, options] = Array.from(arguments); - ({scope} = options); - } - } else { - [keyPath] = Array.from(arguments); - } - - if (scope != null) { - const value = this.getRawScopedValue(scope, keyPath, options); - return value != null ? value : this.getRawValue(keyPath, options); - } else { - return this.getRawValue(keyPath, options); + get () { + let keyPath, options, scope + if (arguments.length > 1) { + if ((typeof arguments[0] === 'string') || (arguments[0] == null)) { + [keyPath, options] = Array.from(arguments); + ({scope} = options) } + } else { + [keyPath] = Array.from(arguments) } + if (scope != null) { + const value = this.getRawScopedValue(scope, keyPath, options) + return value != null ? value : this.getRawValue(keyPath, options) + } else { + return this.getRawValue(keyPath, options) + } + } + // Extended: Get all of the values for the given key-path, along with their // associated scope selector. // @@ -650,36 +648,38 @@ module.exports = // Returns an {Array} of {Object}s with the following keys: // * `scopeDescriptor` The {ScopeDescriptor} with which the value is associated // * `value` The value for the key-path - getAll(keyPath, options) { - let globalValue, result, scope; - if (options != null) { ({scope} = options); } + getAll (keyPath, options) { + let globalValue, result, scope + if (options != null) { ({scope} = options) } - if (scope != null) { - let legacyScopeDescriptor; - const scopeDescriptor = ScopeDescriptor.fromObject(scope); - result = this.scopedSettingsStore.getAll( + if (scope != null) { + let legacyScopeDescriptor + const scopeDescriptor = ScopeDescriptor.fromObject(scope) + result = this.scopedSettingsStore.getAll( scopeDescriptor.getScopeChain(), keyPath, options - ); - if (legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor)) { - result.push(...Array.from(this.scopedSettingsStore.getAll( + ) + legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor) + if (legacyScopeDescriptor) { + result.push(...Array.from(this.scopedSettingsStore.getAll( legacyScopeDescriptor.getScopeChain(), keyPath, options - ) || [])); - } - } else { - result = []; + ) || [])) } - - if (globalValue = this.getRawValue(keyPath, options)) { - result.push({scopeSelector: '*', value: globalValue}); - } - - return result; + } else { + result = [] } + globalValue = this.getRawValue(keyPath, options) + if (globalValue) { + result.push({scopeSelector: '*', value: globalValue}) + } + + return result + } + // Essential: Sets the value for a configuration setting. // // This value is stored in Atom's internal configuration file. @@ -722,88 +722,88 @@ module.exports = // Returns a {Boolean} // * `true` if the value was set. // * `false` if the value was not able to be coerced to the type specified in the setting's schema. - set() { - let [keyPath, value, options] = Array.from(arguments); + set () { + let [keyPath, value, options] = Array.from(arguments) - if (!this.settingsLoaded) { - this.pendingOperations.push(() => this.set.call(this, keyPath, value, options)); - } - - const scopeSelector = options != null ? options.scopeSelector : undefined; - let source = options != null ? options.source : undefined; - const shouldSave = (options != null ? options.save : undefined) != null ? (options != null ? options.save : undefined) : true; - - if (source && !scopeSelector) { - throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!"); - } - - if (source == null) { source = this.getUserConfigPath(); } - - if (value !== undefined) { - try { - value = this.makeValueConformToSchema(keyPath, value); - } catch (e) { - return false; - } - } - - if (scopeSelector != null) { - this.setRawScopedValue(keyPath, value, source, scopeSelector); - } else { - this.setRawValue(keyPath, value); - } - - if ((source === this.getUserConfigPath()) && shouldSave && !this.configFileHasErrors && this.settingsLoaded) { - this.requestSave(); - } - return true; + if (!this.settingsLoaded) { + this.pendingOperations.push(() => this.set.call(this, keyPath, value, options)) } + const scopeSelector = options != null ? options.scopeSelector : undefined + let source = options != null ? options.source : undefined + const shouldSave = (options != null ? options.save : undefined) != null ? (options != null ? options.save : undefined) : true + + if (source && !scopeSelector) { + throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!") + } + + if (source == null) { source = this.getUserConfigPath() } + + if (value !== undefined) { + try { + value = this.makeValueConformToSchema(keyPath, value) + } catch (e) { + return false + } + } + + if (scopeSelector != null) { + this.setRawScopedValue(keyPath, value, source, scopeSelector) + } else { + this.setRawValue(keyPath, value) + } + + if ((source === this.getUserConfigPath()) && shouldSave && !this.configFileHasErrors && this.settingsLoaded) { + this.requestSave() + } + return true + } + // Essential: Restore the setting at `keyPath` to its default value. // // * `keyPath` The {String} name of the key. // * `options` (optional) {Object} // * `scopeSelector` (optional) {String}. See {::set} // * `source` (optional) {String}. See {::set} - unset(keyPath, options) { - if (!this.settingsLoaded) { - this.pendingOperations.push(() => this.unset.call(this, keyPath, options)); - } + unset (keyPath, options) { + if (!this.settingsLoaded) { + this.pendingOperations.push(() => this.unset.call(this, keyPath, options)) + } - let {scopeSelector, source} = options != null ? options : {}; - if (source == null) { source = this.getUserConfigPath(); } + let {scopeSelector, source} = options != null ? options : {} + if (source == null) { source = this.getUserConfigPath() } - if (scopeSelector != null) { - if (keyPath != null) { - let settings = this.scopedSettingsStore.propertiesForSourceAndSelector(source, scopeSelector); - if (getValueAtKeyPath(settings, keyPath) != null) { - this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector); - setValueAtKeyPath(settings, keyPath, undefined); - settings = withoutEmptyObjects(settings); - if (settings != null) { this.set(null, settings, {scopeSelector, source, priority: this.priorityForSource(source)}); } - if ((source === this.getUserConfigPath()) && !this.configFileHasErrors && this.settingsLoaded) { - return this.requestSave(); - } + if (scopeSelector != null) { + if (keyPath != null) { + let settings = this.scopedSettingsStore.propertiesForSourceAndSelector(source, scopeSelector) + if (getValueAtKeyPath(settings, keyPath) != null) { + this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) + setValueAtKeyPath(settings, keyPath, undefined) + settings = withoutEmptyObjects(settings) + if (settings != null) { this.set(null, settings, {scopeSelector, source, priority: this.priorityForSource(source)}) } + if ((source === this.getUserConfigPath()) && !this.configFileHasErrors && this.settingsLoaded) { + return this.requestSave() } - } else { - this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector); - return this.emitChangeEvent(); } } else { - for (scopeSelector in this.scopedSettingsStore.propertiesForSource(source)) { - this.unset(keyPath, {scopeSelector, source}); - } - if ((keyPath != null) && (source === this.getUserConfigPath())) { - return this.set(keyPath, getValueAtKeyPath(this.defaultSettings, keyPath)); - } + this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) + return this.emitChangeEvent() + } + } else { + for (scopeSelector in this.scopedSettingsStore.propertiesForSource(source)) { + this.unset(keyPath, {scopeSelector, source}) + } + if ((keyPath != null) && (source === this.getUserConfigPath())) { + return this.set(keyPath, getValueAtKeyPath(this.defaultSettings, keyPath)) } } + } // Extended: Get an {Array} of all of the `source` {String}s with which // settings have been added via {::set}. - getSources() { - return _.uniq(_.pluck(this.scopedSettingsStore.propertySets, 'source')).sort(); - } + getSources () { + return _.uniq(_.pluck(this.scopedSettingsStore.propertySets, 'source')).sort() + } // Extended: Retrieve the schema for a specific key path. The schema will tell // you what type the keyPath expects, and other metadata about the config @@ -814,56 +814,56 @@ module.exports = // Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. // Returns `null` when the keyPath has no schema specified, but is accessible // from the root schema. - getSchema(keyPath) { - const keys = splitKeyPath(keyPath); - let { schema } = this; - for (let key of Array.from(keys)) { - var childSchema; - if (schema.type === 'object') { - childSchema = schema.properties != null ? schema.properties[key] : undefined; - if (childSchema == null) { - if (isPlainObject(schema.additionalProperties)) { - childSchema = schema.additionalProperties; - } else if (schema.additionalProperties === false) { - return null; - } else { - return {type: 'any'}; - } + getSchema (keyPath) { + const keys = splitKeyPath(keyPath) + let { schema } = this + for (let key of Array.from(keys)) { + var childSchema + if (schema.type === 'object') { + childSchema = schema.properties != null ? schema.properties[key] : undefined + if (childSchema == null) { + if (isPlainObject(schema.additionalProperties)) { + childSchema = schema.additionalProperties + } else if (schema.additionalProperties === false) { + return null + } else { + return {type: 'any'} } - } else { - return null; } - schema = childSchema; + } else { + return null } - return schema; + schema = childSchema } + return schema + } // Extended: Get the {String} path to the config file being used. - getUserConfigPath() { - return this.configFilePath; - } + getUserConfigPath () { + return this.configFilePath + } // Extended: Suppress calls to handler functions registered with {::onDidChange} // and {::observe} for the duration of `callback`. After `callback` executes, // handlers will be called once if the value for their key-path has changed. // // * `callback` {Function} to execute while suppressing calls to handlers. - transact(callback) { - this.beginTransaction(); - try { - return callback(); - } finally { - this.endTransaction(); - } + transact (callback) { + this.beginTransaction() + try { + return callback() + } finally { + this.endTransaction() } + } - addLegacyScopeAlias(languageId, legacyScopeName) { - return this.legacyScopeAliases[languageId] = legacyScopeName; - } + addLegacyScopeAlias (languageId, legacyScopeName) { + return this.legacyScopeAliases[languageId] = legacyScopeName + } - removeLegacyScopeAlias(languageId) { - return delete this.legacyScopeAliases[languageId]; - } + removeLegacyScopeAlias (languageId) { + return delete this.legacyScopeAliases[languageId] + } /* Section: Internal methods used by core @@ -880,360 +880,362 @@ module.exports = // Returns a {Promise} that is either resolved or rejected according to the // `{Promise}` returned by `callback`. If `callback` throws an error, a // rejected {Promise} will be returned instead. - transactAsync(callback) { - let endTransaction; - this.beginTransaction(); - try { - endTransaction = fn => (...args) => { - this.endTransaction(); - return fn(...Array.from(args || [])); - }; - const result = callback(); - return new Promise(function(resolve, reject) { - return result.then(endTransaction(resolve)).catch(endTransaction(reject)); - }); - } catch (error) { - this.endTransaction(); - return Promise.reject(error); + transactAsync (callback) { + let endTransaction + this.beginTransaction() + try { + endTransaction = fn => (...args) => { + this.endTransaction() + return fn(...Array.from(args || [])) + } + const result = callback() + return new Promise(function (resolve, reject) { + return result.then(endTransaction(resolve)).catch(endTransaction(reject)) + }) + } catch (error) { + this.endTransaction() + return Promise.reject(error) + } + } + + beginTransaction () { + return this.transactDepth++ + } + + endTransaction () { + this.transactDepth-- + return this.emitChangeEvent() + } + + pushAtKeyPath (keyPath, value) { + let left + const arrayValue = (left = this.get(keyPath)) != null ? left : [] + const result = arrayValue.push(value) + this.set(keyPath, arrayValue) + return result + } + + unshiftAtKeyPath (keyPath, value) { + let left + const arrayValue = (left = this.get(keyPath)) != null ? left : [] + const result = arrayValue.unshift(value) + this.set(keyPath, arrayValue) + return result + } + + removeAtKeyPath (keyPath, value) { + let left + const arrayValue = (left = this.get(keyPath)) != null ? left : [] + const result = _.remove(arrayValue, value) + this.set(keyPath, arrayValue) + return result + } + + setSchema (keyPath, schema) { + if (!isPlainObject(schema)) { + throw new Error(`Error loading schema for ${keyPath}: schemas can only be objects!`) + } + + if (!typeof (schema.type != null)) { + throw new Error(`Error loading schema for ${keyPath}: schema objects must have a type attribute`) + } + + let rootSchema = this.schema + if (keyPath) { + for (let key of Array.from(splitKeyPath(keyPath))) { + rootSchema.type = 'object' + if (rootSchema.properties == null) { rootSchema.properties = {} } + const { properties } = rootSchema + if (properties[key] == null) { properties[key] = {} } + rootSchema = properties[key] } } - beginTransaction() { - return this.transactDepth++; - } + Object.assign(rootSchema, schema) + return this.transact(() => { + this.setDefaults(keyPath, this.extractDefaultsFromSchema(schema)) + this.setScopedDefaultsFromSchema(keyPath, schema) + return this.resetSettingsForSchemaChange() + }) + } - endTransaction() { - this.transactDepth--; - return this.emitChangeEvent(); - } - - pushAtKeyPath(keyPath, value) { - let left; - const arrayValue = (left = this.get(keyPath)) != null ? left : []; - const result = arrayValue.push(value); - this.set(keyPath, arrayValue); - return result; - } - - unshiftAtKeyPath(keyPath, value) { - let left; - const arrayValue = (left = this.get(keyPath)) != null ? left : []; - const result = arrayValue.unshift(value); - this.set(keyPath, arrayValue); - return result; - } - - removeAtKeyPath(keyPath, value) { - let left; - const arrayValue = (left = this.get(keyPath)) != null ? left : []; - const result = _.remove(arrayValue, value); - this.set(keyPath, arrayValue); - return result; - } - - setSchema(keyPath, schema) { - if (!isPlainObject(schema)) { - throw new Error(`Error loading schema for ${keyPath}: schemas can only be objects!`); - } - - if (!typeof (schema.type != null)) { - throw new Error(`Error loading schema for ${keyPath}: schema objects must have a type attribute`); - } - - let rootSchema = this.schema; - if (keyPath) { - for (let key of Array.from(splitKeyPath(keyPath))) { - rootSchema.type = 'object'; - if (rootSchema.properties == null) { rootSchema.properties = {}; } - const { properties } = rootSchema; - if (properties[key] == null) { properties[key] = {}; } - rootSchema = properties[key]; - } - } - - Object.assign(rootSchema, schema); - return this.transact(() => { - this.setDefaults(keyPath, this.extractDefaultsFromSchema(schema)); - this.setScopedDefaultsFromSchema(keyPath, schema); - return this.resetSettingsForSchemaChange(); - }); - } - - load() { - this.initializeConfigDirectory(); - this.loadUserConfig(); - return this.observeUserConfig(); - } + load () { + this.initializeConfigDirectory() + this.loadUserConfig() + return this.observeUserConfig() + } /* Section: Private methods managing the user's config file */ - initializeConfigDirectory(done) { - if (fs.existsSync(this.configDirPath) || this.shouldNotAccessFileSystem()) { return; } + initializeConfigDirectory (done) { + if (fs.existsSync(this.configDirPath) || this.shouldNotAccessFileSystem()) { return } - fs.makeTreeSync(this.configDirPath); + fs.makeTreeSync(this.configDirPath) - const queue = async.queue(({sourcePath, destinationPath}, callback) => fs.copy(sourcePath, destinationPath, callback)); - queue.drain = done; + const queue = async.queue(({sourcePath, destinationPath}, callback) => fs.copy(sourcePath, destinationPath, callback)) + queue.drain = done - const templateConfigDirPath = fs.resolve(this.resourcePath, 'dot-atom'); - const onConfigDirFile = sourcePath => { - const relativePath = sourcePath.substring(templateConfigDirPath.length + 1); - const destinationPath = path.join(this.configDirPath, relativePath); - return queue.push({sourcePath, destinationPath}); - }; - return fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path => true), (function() {})); + const templateConfigDirPath = fs.resolve(this.resourcePath, 'dot-atom') + const onConfigDirFile = sourcePath => { + const relativePath = sourcePath.substring(templateConfigDirPath.length + 1) + const destinationPath = path.join(this.configDirPath, relativePath) + return queue.push({sourcePath, destinationPath}) + } + return fs.traverseTree(templateConfigDirPath, onConfigDirFile, path => true, function () {}) + } + + loadUserConfig () { + let error + if (this.shouldNotAccessFileSystem()) { return } + if (this.savePending) { return } + + try { + if (!fs.existsSync(this.configFilePath)) { + fs.makeTreeSync(path.dirname(this.configFilePath)) + CSON.writeFileSync(this.configFilePath, {}, {flag: 'wx'}) // fails if file exists + } + } catch (error1) { + error = error1 + if (error.code !== 'EEXIST') { + this.configFileHasErrors = true + this.notifyFailure(`Failed to initialize \`${path.basename(this.configFilePath)}\``, error.stack) + return + } } - loadUserConfig() { - let error; - if (this.shouldNotAccessFileSystem()) { return; } - if (this.savePending) { return; } + try { + let userConfig = CSON.readFileSync(this.configFilePath) + if (userConfig === null) { userConfig = {} } - try { - if (!fs.existsSync(this.configFilePath)) { - fs.makeTreeSync(path.dirname(this.configFilePath)); - CSON.writeFileSync(this.configFilePath, {}, {flag: 'wx'}); // fails if file exists - } - } catch (error1) { - error = error1; - if (error.code !== 'EEXIST') { - this.configFileHasErrors = true; - this.notifyFailure(`Failed to initialize \`${path.basename(this.configFilePath)}\``, error.stack); - return; - } + if (!isPlainObject(userConfig)) { + throw new Error(`\`${path.basename(this.configFilePath)}\` must contain valid JSON or CSON`) } - try { - let userConfig = CSON.readFileSync(this.configFilePath); - if (userConfig === null) { userConfig = {}; } + this.resetUserSettings(userConfig) + return this.configFileHasErrors = false + } catch (error2) { + error = error2 + this.configFileHasErrors = true + const message = `Failed to load \`${path.basename(this.configFilePath)}\`` - if (!isPlainObject(userConfig)) { - throw new Error(`\`${path.basename(this.configFilePath)}\` must contain valid JSON or CSON`); - } - - this.resetUserSettings(userConfig); - return this.configFileHasErrors = false; - } catch (error2) { - error = error2; - this.configFileHasErrors = true; - const message = `Failed to load \`${path.basename(this.configFilePath)}\``; - - const detail = (error.location != null) ? + const detail = (error.location != null) // stack is the output from CSON in this case - error.stack + ? error.stack : // message will be EACCES permission denied, et al - error.message; + error.message - return this.notifyFailure(message, detail); - } + return this.notifyFailure(message, detail) } + } - observeUserConfig() { - if (this.shouldNotAccessFileSystem()) { return; } + observeUserConfig () { + if (this.shouldNotAccessFileSystem()) { return } - try { - if (this.watchSubscriptionPromise == null) { this.watchSubscriptionPromise = watchPath(this.configFilePath, {}, events => { + try { + if (this.watchSubscriptionPromise == null) { + this.watchSubscriptionPromise = watchPath(this.configFilePath, {}, events => { return (() => { - const result = []; + const result = [] for (let {action} of Array.from(events)) { if (['created', 'modified', 'renamed'].includes(action) && (this.watchSubscriptionPromise != null)) { - result.push(this.requestLoad()); + result.push(this.requestLoad()) } else { - result.push(undefined); + result.push(undefined) } } - return result; - })(); - }); } - } catch (error) { - this.notifyFailure(`\ + return result + })() + }) + } + } catch (error) { + this.notifyFailure(`\ Unable to watch path: \`${path.basename(this.configFilePath)}\`. Make sure you have permissions to \`${this.configFilePath}\`. On linux there are currently problems with watch sizes. See [this document][watches] for more info. [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path\ ` - ); - } - - return this.watchSubscriptionPromise; + ) } - unobserveUserConfig() { - if (this.watchSubscriptionPromise != null) { - this.watchSubscriptionPromise.then(watcher => watcher != null ? watcher.dispose() : undefined); - } - return this.watchSubscriptionPromise = null; - } + return this.watchSubscriptionPromise + } - notifyFailure(errorMessage, detail) { - return (this.notificationManager != null ? this.notificationManager.addError(errorMessage, {detail, dismissable: true}) : undefined); + unobserveUserConfig () { + if (this.watchSubscriptionPromise != null) { + this.watchSubscriptionPromise.then(watcher => watcher != null ? watcher.dispose() : undefined) } + return this.watchSubscriptionPromise = null + } - save() { - if (this.shouldNotAccessFileSystem()) { return; } + notifyFailure (errorMessage, detail) { + return (this.notificationManager != null ? this.notificationManager.addError(errorMessage, {detail, dismissable: true}) : undefined) + } - let allSettings = {'*': this.settings}; - allSettings = Object.assign(allSettings, this.scopedSettingsStore.propertiesForSource(this.getUserConfigPath())); - allSettings = sortObject(allSettings); - try { - return CSON.writeFileSync(this.configFilePath, allSettings); - } catch (error) { - const message = `Failed to save \`${path.basename(this.configFilePath)}\``; - const detail = error.message; - return this.notifyFailure(message, detail); - } + save () { + if (this.shouldNotAccessFileSystem()) { return } + + let allSettings = {'*': this.settings} + allSettings = Object.assign(allSettings, this.scopedSettingsStore.propertiesForSource(this.getUserConfigPath())) + allSettings = sortObject(allSettings) + try { + return CSON.writeFileSync(this.configFilePath, allSettings) + } catch (error) { + const message = `Failed to save \`${path.basename(this.configFilePath)}\`` + const detail = error.message + return this.notifyFailure(message, detail) } + } /* Section: Private methods managing global settings */ - resetUserSettings(newSettings) { - if (newSettings.global != null) { - newSettings['*'] = newSettings.global; - delete newSettings.global; - } - - if (newSettings['*'] != null) { - const scopedSettings = newSettings; - newSettings = newSettings['*']; - delete scopedSettings['*']; - this.resetUserScopedSettings(scopedSettings); - } - - return this.transact(() => { - this.settings = {}; - this.settingsLoaded = true; - for (let key in newSettings) { const value = newSettings[key]; this.set(key, value, {save: false}); } - if (this.pendingOperations.length) { - for (let op of Array.from(this.pendingOperations)) { op(); } - return this.pendingOperations = []; - } - }); + resetUserSettings (newSettings) { + if (newSettings.global != null) { + newSettings['*'] = newSettings.global + delete newSettings.global } - getRawValue(keyPath, options) { - let defaultValue, value; - if (!(__guard__(options != null ? options.excludeSources : undefined, x => x.indexOf(this.getUserConfigPath())) >= 0)) { - value = getValueAtKeyPath(this.settings, keyPath); - } - if (!(__guard__(options != null ? options.sources : undefined, x1 => x1.length) > 0)) { - defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath); - } + if (newSettings['*'] != null) { + const scopedSettings = newSettings + newSettings = newSettings['*'] + delete scopedSettings['*'] + this.resetUserScopedSettings(scopedSettings) + } - if (value != null) { - value = this.deepClone(value); - if (isPlainObject(value) && isPlainObject(defaultValue)) { this.deepDefaults(value, defaultValue); } + return this.transact(() => { + this.settings = {} + this.settingsLoaded = true + for (let key in newSettings) { const value = newSettings[key]; this.set(key, value, {save: false}) } + if (this.pendingOperations.length) { + for (let op of Array.from(this.pendingOperations)) { op() } + return this.pendingOperations = [] + } + }) + } + + getRawValue (keyPath, options) { + let defaultValue, value + if (!(__guard__(options != null ? options.excludeSources : undefined, x => x.indexOf(this.getUserConfigPath())) >= 0)) { + value = getValueAtKeyPath(this.settings, keyPath) + } + if (!(__guard__(options != null ? options.sources : undefined, x1 => x1.length) > 0)) { + defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath) + } + + if (value != null) { + value = this.deepClone(value) + if (isPlainObject(value) && isPlainObject(defaultValue)) { this.deepDefaults(value, defaultValue) } + } else { + value = this.deepClone(defaultValue) + } + + return value + } + + setRawValue (keyPath, value) { + const defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath) + if (_.isEqual(defaultValue, value)) { + if (keyPath != null) { + deleteValueAtKeyPath(this.settings, keyPath) } else { - value = this.deepClone(defaultValue); + this.settings = null } - - return value; - } - - setRawValue(keyPath, value) { - const defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath); - if (_.isEqual(defaultValue, value)) { - if (keyPath != null) { - deleteValueAtKeyPath(this.settings, keyPath); - } else { - this.settings = null; - } + } else { + if (keyPath != null) { + setValueAtKeyPath(this.settings, keyPath, value) } else { - if (keyPath != null) { - setValueAtKeyPath(this.settings, keyPath, value); - } else { - this.settings = value; - } - } - return this.emitChangeEvent(); - } - - observeKeyPath(keyPath, options, callback) { - callback(this.get(keyPath)); - return this.onDidChangeKeyPath(keyPath, event => callback(event.newValue)); - } - - onDidChangeKeyPath(keyPath, callback) { - let oldValue = this.get(keyPath); - return this.emitter.on('did-change', () => { - const newValue = this.get(keyPath); - if (!_.isEqual(oldValue, newValue)) { - const event = {oldValue, newValue}; - oldValue = newValue; - return callback(event); - } - }); - } - - isSubKeyPath(keyPath, subKeyPath) { - if ((keyPath == null) || (subKeyPath == null)) { return false; } - const pathSubTokens = splitKeyPath(subKeyPath); - const pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length); - return _.isEqual(pathTokens, pathSubTokens); - } - - setRawDefault(keyPath, value) { - setValueAtKeyPath(this.defaultSettings, keyPath, value); - return this.emitChangeEvent(); - } - - setDefaults(keyPath, defaults) { - if ((defaults != null) && isPlainObject(defaults)) { - const keys = splitKeyPath(keyPath); - this.transact(() => { - return (() => { - const result = []; - for (let key in defaults) { - const childValue = defaults[key]; - if (!defaults.hasOwnProperty(key)) { continue; } - result.push(this.setDefaults(keys.concat([key]).join('.'), childValue)); - } - return result; - })(); - }); - } else { - try { - defaults = this.makeValueConformToSchema(keyPath, defaults); - this.setRawDefault(keyPath, defaults); - } catch (e) { - console.warn(`'${keyPath}' could not set the default. Attempted default: ${JSON.stringify(defaults)}; Schema: ${JSON.stringify(this.getSchema(keyPath))}`); - } + this.settings = value } } + return this.emitChangeEvent() + } - deepClone(object) { - if (object instanceof Color) { - return object.clone(); - } else if (_.isArray(object)) { - return object.map(value => this.deepClone(value)); - } else if (isPlainObject(object)) { - return _.mapObject(object, (key, value) => [key, this.deepClone(value)]); - } else { - return object; + observeKeyPath (keyPath, options, callback) { + callback(this.get(keyPath)) + return this.onDidChangeKeyPath(keyPath, event => callback(event.newValue)) + } + + onDidChangeKeyPath (keyPath, callback) { + let oldValue = this.get(keyPath) + return this.emitter.on('did-change', () => { + const newValue = this.get(keyPath) + if (!_.isEqual(oldValue, newValue)) { + const event = {oldValue, newValue} + oldValue = newValue + return callback(event) } - } + }) + } - deepDefaults(target) { - let result = target; - let i = 0; - while (++i < arguments.length) { - const object = arguments[i]; - if (isPlainObject(result) && isPlainObject(object)) { - for (let key of Array.from(Object.keys(object))) { - result[key] = this.deepDefaults(result[key], object[key]); - } - } else { - if ((result == null)) { - result = this.deepClone(object); + isSubKeyPath (keyPath, subKeyPath) { + if ((keyPath == null) || (subKeyPath == null)) { return false } + const pathSubTokens = splitKeyPath(subKeyPath) + const pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length) + return _.isEqual(pathTokens, pathSubTokens) + } + + setRawDefault (keyPath, value) { + setValueAtKeyPath(this.defaultSettings, keyPath, value) + return this.emitChangeEvent() + } + + setDefaults (keyPath, defaults) { + if ((defaults != null) && isPlainObject(defaults)) { + const keys = splitKeyPath(keyPath) + this.transact(() => { + return (() => { + const result = [] + for (let key in defaults) { + const childValue = defaults[key] + if (!defaults.hasOwnProperty(key)) { continue } + result.push(this.setDefaults(keys.concat([key]).join('.'), childValue)) } + return result + })() + }) + } else { + try { + defaults = this.makeValueConformToSchema(keyPath, defaults) + this.setRawDefault(keyPath, defaults) + } catch (e) { + console.warn(`'${keyPath}' could not set the default. Attempted default: ${JSON.stringify(defaults)}; Schema: ${JSON.stringify(this.getSchema(keyPath))}`) + } + } + } + + deepClone (object) { + if (object instanceof Color) { + return object.clone() + } else if (_.isArray(object)) { + return object.map(value => this.deepClone(value)) + } else if (isPlainObject(object)) { + return _.mapObject(object, (key, value) => [key, this.deepClone(value)]) + } else { + return object + } + } + + deepDefaults (target) { + let result = target + let i = 0 + while (++i < arguments.length) { + const object = arguments[i] + if (isPlainObject(result) && isPlainObject(object)) { + for (let key of Array.from(Object.keys(object))) { + result[key] = this.deepDefaults(result[key], object[key]) + } + } else { + if ((result == null)) { + result = this.deepClone(object) } } - return result; } + return result + } // `schema` will look something like this // @@ -1244,166 +1246,162 @@ sizes. See [this document][watches] for more info. // '.source.js': // default: 'omg' // ``` - setScopedDefaultsFromSchema(keyPath, schema) { - if ((schema.scopes != null) && isPlainObject(schema.scopes)) { - const scopedDefaults = {}; - for (let scope in schema.scopes) { - const scopeSchema = schema.scopes[scope]; - if (!scopeSchema.hasOwnProperty('default')) { continue; } - scopedDefaults[scope] = {}; - setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default); - } - this.scopedSettingsStore.addProperties('schema-default', scopedDefaults); + setScopedDefaultsFromSchema (keyPath, schema) { + if ((schema.scopes != null) && isPlainObject(schema.scopes)) { + const scopedDefaults = {} + for (let scope in schema.scopes) { + const scopeSchema = schema.scopes[scope] + if (!scopeSchema.hasOwnProperty('default')) { continue } + scopedDefaults[scope] = {} + setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default) } - - if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { - const keys = splitKeyPath(keyPath); - for (let key in schema.properties) { - const childValue = schema.properties[key]; - if (!schema.properties.hasOwnProperty(key)) { continue; } - this.setScopedDefaultsFromSchema(keys.concat([key]).join('.'), childValue); - } - } - + this.scopedSettingsStore.addProperties('schema-default', scopedDefaults) } - extractDefaultsFromSchema(schema) { - if (schema.default != null) { - return schema.default; - } else if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { - const defaults = {}; - const properties = schema.properties || {}; - for (let key in properties) { const value = properties[key]; defaults[key] = this.extractDefaultsFromSchema(value); } - return defaults; + if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { + const keys = splitKeyPath(keyPath) + for (let key in schema.properties) { + const childValue = schema.properties[key] + if (!schema.properties.hasOwnProperty(key)) { continue } + this.setScopedDefaultsFromSchema(keys.concat([key]).join('.'), childValue) } } + } - makeValueConformToSchema(keyPath, value, options) { - if (options != null ? options.suppressException : undefined) { - try { - return this.makeValueConformToSchema(keyPath, value); - } catch (e) { - return undefined; - } - } else { - let schema; - if ((schema = this.getSchema(keyPath)) == null) { - if (schema === false) { throw new Error(`Illegal key path ${keyPath}`); } - } - return this.constructor.executeSchemaEnforcers(keyPath, value, schema); - } + extractDefaultsFromSchema (schema) { + if (schema.default != null) { + return schema.default + } else if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { + const defaults = {} + const properties = schema.properties || {} + for (let key in properties) { const value = properties[key]; defaults[key] = this.extractDefaultsFromSchema(value) } + return defaults } + } + + makeValueConformToSchema (keyPath, value, options) { + if (options != null ? options.suppressException : undefined) { + try { + return this.makeValueConformToSchema(keyPath, value) + } catch (e) { + return undefined + } + } else { + let schema + if ((schema = this.getSchema(keyPath)) == null) { + if (schema === false) { throw new Error(`Illegal key path ${keyPath}`) } + } + return this.constructor.executeSchemaEnforcers(keyPath, value, schema) + } + } // When the schema is changed / added, there may be values set in the config // that do not conform to the schema. This will reset make them conform. - resetSettingsForSchemaChange(source) { - if (source == null) { source = this.getUserConfigPath(); } - return this.transact(() => { - this.settings = this.makeValueConformToSchema(null, this.settings, {suppressException: true}); - const selectorsAndSettings = this.scopedSettingsStore.propertiesForSource(source); - this.scopedSettingsStore.removePropertiesForSource(source); - for (let scopeSelector in selectorsAndSettings) { - let settings = selectorsAndSettings[scopeSelector]; - settings = this.makeValueConformToSchema(null, settings, {suppressException: true}); - this.setRawScopedValue(null, settings, source, scopeSelector); - } - }); - } + resetSettingsForSchemaChange (source) { + if (source == null) { source = this.getUserConfigPath() } + return this.transact(() => { + this.settings = this.makeValueConformToSchema(null, this.settings, {suppressException: true}) + const selectorsAndSettings = this.scopedSettingsStore.propertiesForSource(source) + this.scopedSettingsStore.removePropertiesForSource(source) + for (let scopeSelector in selectorsAndSettings) { + let settings = selectorsAndSettings[scopeSelector] + settings = this.makeValueConformToSchema(null, settings, {suppressException: true}) + this.setRawScopedValue(null, settings, source, scopeSelector) + } + }) + } /* Section: Private Scoped Settings */ - priorityForSource(source) { - if (source === this.getUserConfigPath()) { - return 1000; - } else { - return 0; - } + priorityForSource (source) { + if (source === this.getUserConfigPath()) { + return 1000 + } else { + return 0 + } + } + + emitChangeEvent () { + if (!(this.transactDepth > 0)) { return this.emitter.emit('did-change') } + } + + resetUserScopedSettings (newScopedSettings) { + const source = this.getUserConfigPath() + const priority = this.priorityForSource(source) + this.scopedSettingsStore.removePropertiesForSource(source) + + for (let scopeSelector in newScopedSettings) { + let settings = newScopedSettings[scopeSelector] + settings = this.makeValueConformToSchema(null, settings, {suppressException: true}) + const validatedSettings = {} + validatedSettings[scopeSelector] = withoutEmptyObjects(settings) + if (validatedSettings[scopeSelector] != null) { this.scopedSettingsStore.addProperties(source, validatedSettings, {priority}) } } - emitChangeEvent() { - if (!(this.transactDepth > 0)) { return this.emitter.emit('did-change'); } + return this.emitChangeEvent() + } + + setRawScopedValue (keyPath, value, source, selector, options) { + if (keyPath != null) { + const newValue = {} + setValueAtKeyPath(newValue, keyPath, value) + value = newValue } - resetUserScopedSettings(newScopedSettings) { - const source = this.getUserConfigPath(); - const priority = this.priorityForSource(source); - this.scopedSettingsStore.removePropertiesForSource(source); + const settingsBySelector = {} + settingsBySelector[selector] = value + this.scopedSettingsStore.addProperties(source, settingsBySelector, {priority: this.priorityForSource(source)}) + return this.emitChangeEvent() + } - for (let scopeSelector in newScopedSettings) { - let settings = newScopedSettings[scopeSelector]; - settings = this.makeValueConformToSchema(null, settings, {suppressException: true}); - const validatedSettings = {}; - validatedSettings[scopeSelector] = withoutEmptyObjects(settings); - if (validatedSettings[scopeSelector] != null) { this.scopedSettingsStore.addProperties(source, validatedSettings, {priority}); } - } - - return this.emitChangeEvent(); - } - - setRawScopedValue(keyPath, value, source, selector, options) { - if (keyPath != null) { - const newValue = {}; - setValueAtKeyPath(newValue, keyPath, value); - value = newValue; - } - - const settingsBySelector = {}; - settingsBySelector[selector] = value; - this.scopedSettingsStore.addProperties(source, settingsBySelector, {priority: this.priorityForSource(source)}); - return this.emitChangeEvent(); - } - - getRawScopedValue(scopeDescriptor, keyPath, options) { - let legacyScopeDescriptor; - scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor); - const result = this.scopedSettingsStore.getPropertyValue( + getRawScopedValue (scopeDescriptor, keyPath, options) { + let legacyScopeDescriptor + scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor) + const result = this.scopedSettingsStore.getPropertyValue( scopeDescriptor.getScopeChain(), keyPath, options - ); + ) - if (result != null) { - return result; - } else if (legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor)) { - return this.scopedSettingsStore.getPropertyValue( + if (result != null) { + return result + } else if (legacyScopeDescriptor = this.getLegacyScopeDescriptor(scopeDescriptor)) { + return this.scopedSettingsStore.getPropertyValue( legacyScopeDescriptor.getScopeChain(), keyPath, options - ); + ) + } + } + + observeScopedKeyPath (scope, keyPath, callback) { + callback(this.get(keyPath, {scope})) + return this.onDidChangeScopedKeyPath(scope, keyPath, event => callback(event.newValue)) + } + + onDidChangeScopedKeyPath (scope, keyPath, callback) { + let oldValue = this.get(keyPath, {scope}) + return this.emitter.on('did-change', () => { + const newValue = this.get(keyPath, {scope}) + if (!_.isEqual(oldValue, newValue)) { + const event = {oldValue, newValue} + oldValue = newValue + return callback(event) } - } + }) + } - observeScopedKeyPath(scope, keyPath, callback) { - callback(this.get(keyPath, {scope})); - return this.onDidChangeScopedKeyPath(scope, keyPath, event => callback(event.newValue)); - } - - onDidChangeScopedKeyPath(scope, keyPath, callback) { - let oldValue = this.get(keyPath, {scope}); - return this.emitter.on('did-change', () => { - const newValue = this.get(keyPath, {scope}); - if (!_.isEqual(oldValue, newValue)) { - const event = {oldValue, newValue}; - oldValue = newValue; - return callback(event); - } - }); - } - - getLegacyScopeDescriptor(scopeDescriptor) { - const legacyAlias = this.legacyScopeAliases[scopeDescriptor.scopes[0]]; - if (legacyAlias) { - const scopes = scopeDescriptor.scopes.slice(); - scopes[0] = legacyAlias; - return new ScopeDescriptor({scopes}); - } + getLegacyScopeDescriptor (scopeDescriptor) { + const legacyAlias = this.legacyScopeAliases[scopeDescriptor.scopes[0]] + if (legacyAlias) { + const scopes = scopeDescriptor.scopes.slice() + scopes[0] = legacyAlias + return new ScopeDescriptor({scopes}) } + } }; - Config.initClass(); - return Config; -})()); // Base schema enforcers. These will coerce raw input into the specified type, // and will throw an error when the value cannot be coerced. Throwing the error @@ -1415,198 +1413,198 @@ sizes. See [this document][watches] for more info. // specification. Config.addSchemaEnforcers({ 'any': { - coerce(keyPath, value, schema) { - return value; + coerce (keyPath, value, schema) { + return value } }, 'integer': { - coerce(keyPath, value, schema) { - value = parseInt(value); - if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into an int`); } - return value; + coerce (keyPath, value, schema) { + value = parseInt(value) + if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into an int`) } + return value } }, 'number': { - coerce(keyPath, value, schema) { - value = parseFloat(value); - if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a number`); } - return value; + coerce (keyPath, value, schema) { + value = parseFloat(value) + if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a number`) } + return value } }, 'boolean': { - coerce(keyPath, value, schema) { + coerce (keyPath, value, schema) { switch (typeof value) { case 'string': if (value.toLowerCase() === 'true') { - return true; + return true } else if (value.toLowerCase() === 'false') { - return false; + return false } else { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`); + throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`) } case 'boolean': - return value; + return value default: - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`); + throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`) } } }, 'string': { - validate(keyPath, value, schema) { + validate (keyPath, value, schema) { if (typeof value !== 'string') { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a string`); + throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a string`) } - return value; + return value }, - validateMaximumLength(keyPath, value, schema) { + validateMaximumLength (keyPath, value, schema) { if ((typeof schema.maximumLength === 'number') && (value.length > schema.maximumLength)) { - return value.slice(0, schema.maximumLength); + return value.slice(0, schema.maximumLength) } else { - return value; + return value } } }, 'null': { // null sort of isnt supported. It will just unset in this case - coerce(keyPath, value, schema) { - if (![undefined, null].includes(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be null`); } - return value; + coerce (keyPath, value, schema) { + if (![undefined, null].includes(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be null`) } + return value } }, 'object': { - coerce(keyPath, value, schema) { - if (!isPlainObject(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an object`); } - if (schema.properties == null) { return value; } + coerce (keyPath, value, schema) { + if (!isPlainObject(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an object`) } + if (schema.properties == null) { return value } - let defaultChildSchema = null; - let allowsAdditionalProperties = true; + let defaultChildSchema = null + let allowsAdditionalProperties = true if (isPlainObject(schema.additionalProperties)) { - defaultChildSchema = schema.additionalProperties; + defaultChildSchema = schema.additionalProperties } if (schema.additionalProperties === false) { - allowsAdditionalProperties = false; + allowsAdditionalProperties = false } - const newValue = {}; + const newValue = {} for (let prop in value) { - const propValue = value[prop]; - const childSchema = schema.properties[prop] != null ? schema.properties[prop] : defaultChildSchema; + const propValue = value[prop] + const childSchema = schema.properties[prop] != null ? schema.properties[prop] : defaultChildSchema if (childSchema != null) { try { - newValue[prop] = this.executeSchemaEnforcers(pushKeyPath(keyPath, prop), propValue, childSchema); + newValue[prop] = this.executeSchemaEnforcers(pushKeyPath(keyPath, prop), propValue, childSchema) } catch (error) { - console.warn(`Error setting item in object: ${error.message}`); + console.warn(`Error setting item in object: ${error.message}`) } } else if (allowsAdditionalProperties) { // Just pass through un-schema'd values - newValue[prop] = propValue; + newValue[prop] = propValue } else { - console.warn(`Illegal object key: ${keyPath}.${prop}`); + console.warn(`Illegal object key: ${keyPath}.${prop}`) } } - return newValue; + return newValue } }, 'array': { - coerce(keyPath, value, schema) { - if (!Array.isArray(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an array`); } - const itemSchema = schema.items; + coerce (keyPath, value, schema) { + if (!Array.isArray(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an array`) } + const itemSchema = schema.items if (itemSchema != null) { - const newValue = []; + const newValue = [] for (let item of Array.from(value)) { try { - newValue.push(this.executeSchemaEnforcers(keyPath, item, itemSchema)); + newValue.push(this.executeSchemaEnforcers(keyPath, item, itemSchema)) } catch (error) { - console.warn(`Error setting item in array: ${error.message}`); + console.warn(`Error setting item in array: ${error.message}`) } } - return newValue; + return newValue } else { - return value; + return value } } }, 'color': { - coerce(keyPath, value, schema) { - const color = Color.parse(value); + coerce (keyPath, value, schema) { + const color = Color.parse(value) if (color == null) { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a color`); + throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a color`) } - return color; + return color } }, '*': { - coerceMinimumAndMaximum(keyPath, value, schema) { - if (typeof value !== 'number') { return value; } + coerceMinimumAndMaximum (keyPath, value, schema) { + if (typeof value !== 'number') { return value } if ((schema.minimum != null) && (typeof schema.minimum === 'number')) { - value = Math.max(value, schema.minimum); + value = Math.max(value, schema.minimum) } if ((schema.maximum != null) && (typeof schema.maximum === 'number')) { - value = Math.min(value, schema.maximum); + value = Math.min(value, schema.maximum) } - return value; + return value }, - validateEnum(keyPath, value, schema) { - let possibleValues = schema.enum; + validateEnum (keyPath, value, schema) { + let possibleValues = schema.enum if (Array.isArray(possibleValues)) { - possibleValues = possibleValues.map(function(value) { - if (value.hasOwnProperty('value')) { return value.value; } else { return value; } - }); + possibleValues = possibleValues.map(function (value) { + if (value.hasOwnProperty('value')) { return value.value } else { return value } + }) } - if ((possibleValues == null) || !Array.isArray(possibleValues) || !possibleValues.length) { return value; } + if ((possibleValues == null) || !Array.isArray(possibleValues) || !possibleValues.length) { return value } for (let possibleValue of Array.from(possibleValues)) { // Using `isEqual` for possibility of placing enums on array and object schemas - if (_.isEqual(possibleValue, value)) { return value; } + if (_.isEqual(possibleValue, value)) { return value } } - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} is not one of ${JSON.stringify(possibleValues)}`); + throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} is not one of ${JSON.stringify(possibleValues)}`) } } -}); +}) -var isPlainObject = value => _.isObject(value) && !_.isArray(value) && !_.isFunction(value) && !_.isString(value) && !(value instanceof Color); +var isPlainObject = value => _.isObject(value) && !_.isArray(value) && !_.isFunction(value) && !_.isString(value) && !(value instanceof Color) -var sortObject = function(value) { - if (!isPlainObject(value)) { return value; } - const result = {}; +var sortObject = function (value) { + if (!isPlainObject(value)) { return value } + const result = {} for (let key of Array.from(Object.keys(value).sort())) { - result[key] = sortObject(value[key]); + result[key] = sortObject(value[key]) } - return result; -}; + return result +} -var withoutEmptyObjects = function(object) { - let resultObject = undefined; +var withoutEmptyObjects = function (object) { + let resultObject if (isPlainObject(object)) { for (let key in object) { - const value = object[key]; - const newValue = withoutEmptyObjects(value); + const value = object[key] + const newValue = withoutEmptyObjects(value) if (newValue != null) { - if (resultObject == null) { resultObject = {}; } - resultObject[key] = newValue; + if (resultObject == null) { resultObject = {} } + resultObject[key] = newValue } } } else { - resultObject = object; + resultObject = object } - return resultObject; -}; + return resultObject +} -function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file +function __guard__ (value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined +}