configorama 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/main.js CHANGED
@@ -3,9 +3,8 @@ const path = require('path')
3
3
  const fs = require('fs')
4
4
  const promiseFinallyShim = require('promise.prototype.finally').shim()
5
5
  // @TODO only import lodash we need
6
- const _ = require('lodash')
6
+
7
7
  const findUp = require('find-up')
8
- const replaceall = require('replaceall')
9
8
  const traverse = require('traverse')
10
9
  const dotProp = require('dot-prop')
11
10
  /* Default Value resolvers */
@@ -21,16 +20,29 @@ const TOML = require('./parsers/toml')
21
20
  const md5Function = require('./functions/md5')
22
21
 
23
22
  /* Utility/helpers */
24
- const splitByComma = require('./utils/splitByComma')
25
23
  const cleanVariable = require('./utils/cleanVariable')
26
24
  const appendDeepVariable = require('./utils/appendDeepVariable')
27
25
  const isValidValue = require('./utils/isValidValue')
28
26
  const PromiseTracker = require('./utils/PromiseTracker')
29
27
  const handleSignalEvents = require('./utils/handleSignalEvents')
30
28
  const formatFunctionArgs = require('./utils/formatFunctionArgs')
31
- const cloudFormationSchema = require('./utils/cloudformationSchema')
32
29
  const trimSurroundingQuotes = require('./utils/trimSurroundingQuotes')
33
30
  const deepLog = require('./utils/deep-log')
31
+ const { splitByComma } = require('./utils/splitByComma')
32
+ const {
33
+ isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
34
+ isEmpty, trim, camelCase, kebabCase, capitalize, split, map, mapValues,
35
+ assign, set, cloneDeep
36
+ } = require('./utils/lodash')
37
+ const { parseFileContents } = require('./utils/parse')
38
+ const { splitCsv } = require('./utils/splitCsv')
39
+ const { replaceAll } = require('./utils/replaceAll')
40
+ const { getTextAfterOccurance, findNestedVariable } = require('./utils/textUtils')
41
+ const { getFallbackString, verifyVariable } = require('./utils/variableUtils')
42
+ const { encodeUnknown, decodeUnknown } = require('./utils/unknownValues')
43
+ const { mergeByKeys } = require('./utils/mergeByKeys')
44
+ const { arrayToJsonPath } = require('./utils/arrayToJsonPath')
45
+
34
46
  /**
35
47
  * Maintainer's notes:
36
48
  *
@@ -45,8 +57,10 @@ const deepLog = require('./utils/deep-log')
45
57
  * pause population, noting the continued depth to traverse. This motivated "deep" variables.
46
58
  * Original issue #4687
47
59
  */
48
-
49
60
  const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
61
+ const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
62
+ const deepIndexPattern = /deep\:(\d*)/
63
+ const deepPrefixReplacePattern = /(?:^deep:)\d+\.?/g
50
64
  const fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-\/,'" ]+?)\)/g)
51
65
  // TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
52
66
  // To match file(asyncValue.js, lol) input params
@@ -58,7 +72,9 @@ const funcStartOfLineRegex = /^(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
58
72
  const subFunctionRegex = /(\w+):(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
59
73
  const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
60
74
  const logLines = '─────────────────────────────────────────────────'
61
- const DEBUG = false
75
+
76
+ let DEBUG = process.argv.includes('--debug') ? true : false
77
+ // DEBUG = true
62
78
 
63
79
  const ENABLE_FUNCTIONS = true
64
80
 
@@ -77,12 +93,14 @@ class Configorama {
77
93
  allowUndefinedValues: false,
78
94
  }, options)
79
95
 
80
-
81
- const defaultSyntax = '\\${((?!AWS)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
96
+ this.filterCache = {}
97
+
98
+ const defaultSyntax = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
99
+
82
100
  const variableSyntax = options.syntax || defaultSyntax
83
101
  let varRegex
84
102
  if (typeof variableSyntax === 'string') {
85
- varRegex = RegExp(variableSyntax, 'g')
103
+ varRegex = new RegExp(variableSyntax, 'g')
86
104
  // this.variableSyntax = /\${((?!AWS)([ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?|(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*))}/
87
105
  } else if (variableSyntax instanceof RegExp) {
88
106
  varRegex = variableSyntax
@@ -94,7 +112,7 @@ class Configorama {
94
112
  // set config objects
95
113
  this.config = fileOrObject
96
114
  // Keep a copy
97
- this.originalConfig = _.cloneDeep(fileOrObject)
115
+ this.originalConfig = cloneDeep(fileOrObject)
98
116
  // Set configPath for file references
99
117
  this.configPath = options.configDir || process.cwd()
100
118
  } else if (typeof fileOrObject === 'string') {
@@ -103,51 +121,19 @@ class Configorama {
103
121
  const fileDirectory = path.dirname(path.resolve(fileOrObject))
104
122
  const fileType = path.extname(fileOrObject)
105
123
 
106
- let configObject
107
- if (fileType.match(/\.(yml|yaml)/)) {
108
- try {
109
- const ymlText = YAML.preProcess(fileContents, varRegex)
110
- // console.log('ymlText', ymlText)
111
- configObject = YAML.parse(ymlText)
112
- } catch (err) {
113
- // Attempt to fix cloudformation refs
114
- if (err.message.match(/YAMLException/)) {
115
- const ymlText = YAML.preProcess(fileContents, varRegex)
116
- const result = YAML.load(ymlText, {
117
- filename: fileOrObject,
118
- schema: cloudFormationSchema.schema
119
- })
120
- if (result.error) {
121
- throw result.error
122
- }
123
- configObject = result.data
124
- }
125
- }
126
- } else if (fileType.match(/\.(toml)/)) {
127
- configObject = TOML.parse(fileContents)
128
- } else if (fileType.match(/\.(json)/)) {
129
- configObject = JSON.parse(fileContents)
130
- } else if (fileType.match(/\.(js)/)) {
131
- let jsFile
132
- try {
133
- jsFile = require(fileOrObject)
134
- if (typeof jsFile !== 'function') {
135
- configObject = jsFile
136
- } else {
137
- let jsArgs = opts.dynamicArgs || {}
138
- if (jsArgs && typeof jsArgs === 'function') {
139
- jsArgs = jsArgs()
140
- }
141
- configObject = jsFile(jsArgs)
142
- }
143
- } catch (err) {
144
- throw new Error(err)
145
- }
146
- }
124
+ // Parse file contents using extracted function
125
+ const configObject = parseFileContents(
126
+ fileContents,
127
+ fileType,
128
+ fileOrObject,
129
+ varRegex,
130
+ this.opts
131
+ )
132
+
147
133
  // set config objects
148
134
  this.config = configObject
149
135
  // Keep a copy
150
- this.originalConfig = _.cloneDeep(configObject)
136
+ this.originalConfig = cloneDeep(configObject)
151
137
  // Set configPath for file references
152
138
  this.configPath = fileDirectory
153
139
  }
@@ -182,7 +168,7 @@ class Configorama {
182
168
  match: selfRefSyntax,
183
169
  resolver: (varString, o, x, pathValue) => {
184
170
  return this.getValueFromSelf(varString, o, x, pathValue)
185
- }
171
+ },
186
172
  },
187
173
  /**
188
174
  * File references
@@ -195,7 +181,7 @@ class Configorama {
195
181
  resolver: (varString, o, x, pathValue) => {
196
182
  // console.log('pathValue getValueFromFile', pathValue)
197
183
  return this.getValueFromFile(varString)
198
- }
184
+ },
199
185
  },
200
186
 
201
187
  // Git refs
@@ -207,20 +193,14 @@ class Configorama {
207
193
  // return this.getValueFromFunction(varString)
208
194
  // }
209
195
  // },
210
- // getValueFromString,
211
- {
212
- match: RegExp(/(?:('|").*?\1)/g),
213
- resolver: (varString, o, x, pathValue) => {
214
- // console.log('pathValue getValueFromString', pathValue)
215
- return this.getValueFromString(varString)
216
- }
217
- },
218
- // Deep references
196
+ /* Resolve string references */
197
+ getValueFromString,
198
+ /* Resolve deep references */
219
199
  {
220
200
  match: deepRefSyntax,
221
201
  resolver: (varString) => {
222
202
  return this.getValueFromDeep(varString)
223
- }
203
+ },
224
204
  },
225
205
  // Numbers
226
206
  getValueFromNumber,
@@ -251,7 +231,7 @@ class Configorama {
251
231
  },
252
232
  resolver: (varString, o, x, pathValue) => {
253
233
  return this.getValueFromSelf(varString, o, x, pathValue)
254
- }
234
+ },
255
235
  }
256
236
 
257
237
  /* Apply user defined variable sources */
@@ -264,6 +244,9 @@ class Configorama {
264
244
 
265
245
  // Additional filters on values. ${thing | filterFunction}
266
246
  this.filters = {
247
+ capitalize: (val) => {
248
+ return capitalize(val)
249
+ },
267
250
  toUpperCase: (val) => {
268
251
  if (typeof val === 'string') {
269
252
  return val.toUpperCase()
@@ -283,14 +266,28 @@ class Configorama {
283
266
  }
284
267
  },
285
268
  toCamelCase: (val) => {
286
- return _.camelCase(val)
269
+ return camelCase(val)
287
270
  },
288
271
  toKebabCase: (val) => {
289
- return _.kebabCase(val)
272
+ return kebabCase(val)
273
+ },
274
+ /* Type filters */
275
+ toNumber: (val, from) => {
276
+ const newVal = Number(val)
277
+ return newVal
278
+ },
279
+ toString: (val) => {
280
+ return String(val)
281
+ },
282
+ toBoolean: (val) => {
283
+ return Boolean(val)
284
+ },
285
+ toJson: (val) => {
286
+ return JSON.stringify(val)
287
+ },
288
+ toObject: (val) => {
289
+ return JSON.parse(val)
290
290
  },
291
- capitalize: (val) => {
292
- return _.capitalize(val)
293
- }
294
291
  }
295
292
 
296
293
  // Apply user defined filters
@@ -298,17 +295,21 @@ class Configorama {
298
295
  this.filters = Object.assign({}, this.filters, options.filters)
299
296
  }
300
297
 
298
+ // (\|\s*(toUpperCase|toLowerCase|toCamelCase|toKebabCase|capitalize)\s*)+$
299
+ this.filterMatch = new RegExp(`(\\|\\s*(${Object.keys(this.filters).join('|')})\\s*)+}?$`)
300
+ // console.log('this.filterMatch', this.filterMatch)
301
+
301
302
  this.functions = {
302
303
  split: (value, delimiter, limit) => {
303
304
  const delimit = delimiter || ','
304
- const splitVal = _.split(value, delimit)
305
+ const splitVal = split(value, delimit)
305
306
  return splitVal
306
307
  },
307
308
  join: (value, delimiter) => {
308
- if (_.isString(value)) {
309
+ if (isString(value)) {
309
310
  value = [value]
310
311
  }
311
- if (!Array.isArray(value)) {
312
+ if (!isArray(value)) {
312
313
  throw new Error('value must be array for join() function')
313
314
  }
314
315
  const delimit = delimiter || ','
@@ -334,16 +335,16 @@ class Configorama {
334
335
  if (typeof value === 'string' && typeof otherValue === 'string') {
335
336
  return value + otherValue
336
337
  }
337
- if (Array.isArray(value) && Array.isArray(otherValue)) {
338
+ if (isArray(value) && isArray(otherValue)) {
338
339
  return otherValue.concat(value)
339
340
  }
340
- return Object.assign({}, value, otherValue)
341
+ return assign({}, value, otherValue)
341
342
  },
342
343
  math: () => {},
343
344
  upperKeys: (o) => {
344
- return Object.keys(o).reduce((c, k) => (c[k.toUpperCase()] = o[k], c), {}) // eslint-disable-line
345
+ return Object.keys(o).reduce((c, k) => ((c[k.toUpperCase()] = o[k]), c), {}) // eslint-disable-line
345
346
  },
346
- md5: md5Function
347
+ md5: md5Function,
347
348
  }
348
349
 
349
350
  // Apply user defined functions
@@ -365,7 +366,7 @@ class Configorama {
365
366
  }
366
367
 
367
368
  /**
368
- * Populate all variables in the service, conviently remove and restore the service attributes
369
+ * Populate all variables in the service, conveniently remove and restore the service attributes
369
370
  * that confuse the population methods.
370
371
  * @param cliOpts An options hive to use for ${opt:...} variables.
371
372
  * @returns {Promise.<TResult>|*} A promise resolving to the populated service.
@@ -375,66 +376,72 @@ class Configorama {
375
376
  const configoramaOpts = this.opts
376
377
  const originalConfig = this.originalConfig
377
378
  return this.initialCall(() => {
378
- return Promise.resolve().then(() => {
379
- return this.populateObjectImpl(this.config).finally(() => {
380
- // TODO populate function values here?
381
- // console.log('Final Config', this.config)
382
- const transform = this.runFunction.bind(this)
383
- const varSyntax = this.variableSyntax
384
- // Traverse resolved object and run functions
385
- // console.log('this.config', this.config)
386
- traverse(this.config).forEach(function (rawValue) {
387
- /* Pass through unknown variables */
388
- if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
389
- const configValuePath = this.path.join('.')
390
- const ogValue = dotProp.get(originalConfig, configValuePath)
391
- const varDisplay = (ogValue) ? `"${ogValue}" variable` : 'variable'
392
- const errorMessage = `
379
+ return Promise.resolve()
380
+ .then(() => {
381
+ return this.populateObjectImpl(this.config).finally(() => {
382
+ // TODO populate function values here?
383
+ // console.log('Final Config', this.config)
384
+ const transform = this.runFunction.bind(this)
385
+ const varSyntax = this.variableSyntax
386
+ // Traverse resolved object and run functions
387
+ // console.log('this.config', this.config)
388
+ traverse(this.config).forEach(function (rawValue) {
389
+ /* Pass through unknown variables */
390
+ if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
391
+ const configValuePath = this.path.join('.')
392
+ const ogValue = dotProp.get(originalConfig, configValuePath)
393
+ const varDisplay = ogValue ? `"${ogValue}" variable` : 'variable'
394
+ const errorMessage = `
393
395
  Config error:
394
396
  "${configValuePath}" resolved to "undefined"
395
397
  Verify the ${varDisplay} in config at "${configValuePath}"`
396
- throw new Error(errorMessage)
397
- }
398
- if (typeof rawValue === 'string') {
399
- /* Process inline functions like merge() */
400
- if (ENABLE_FUNCTIONS && rawValue.match(/> function /)) {
401
- // console.log('RAW FUNCTION', rawFunction)
402
- const funcString = rawValue.replace(/> function /g, '')
403
- // console.log('funcString', funcString)
404
- const func = cleanVariable(funcString, varSyntax, true)
405
- const funcVal = transform(func)
406
- const hasObjectRef = rawValue.match(/\.\S*$/)
407
- if (hasObjectRef && typeof funcVal === 'object') {
408
- const objectPath = hasObjectRef[0].replace(/^\./, '')
409
- // console.log('objectPath', objectPath)
410
- /* get value from object and update */
411
- const valueFromObject = dotProp.get(funcVal, objectPath)
412
- // console.log('valueFromObject', valueFromObject)
413
- this.update(valueFromObject)
414
- } else {
415
- this.update(funcVal)
416
- }
398
+ throw new Error(errorMessage)
417
399
  }
418
-
419
-
420
- /* Allow for unknown variables to pass through */
421
- if (rawValue.match(/>passthrough/)) {
422
- const newValues = decodeUnknown(rawValue)
423
- // console.log('>>>> newValues', newValues)
424
- this.update(newValues)
400
+ if (typeof rawValue === 'string') {
401
+ /* Process inline functions like merge() */
402
+ if (ENABLE_FUNCTIONS && rawValue.match(/> function /)) {
403
+ // console.log('RAW FUNCTION', rawFunction)
404
+ const funcString = rawValue.replace(/> function /g, '')
405
+ // console.log('funcString', funcString)
406
+ const func = cleanVariable(funcString, varSyntax, true)
407
+ const funcVal = transform(func)
408
+ const hasObjectRef = rawValue.match(/\.\S*$/)
409
+ if (hasObjectRef && typeof funcVal === 'object') {
410
+ const objectPath = hasObjectRef[0].replace(/^\./, '')
411
+ // console.log('objectPath', objectPath)
412
+ /* get value from object and update */
413
+ const valueFromObject = dotProp.get(funcVal, objectPath)
414
+ // console.log('valueFromObject', valueFromObject)
415
+ this.update(valueFromObject)
416
+ } else {
417
+ this.update(funcVal)
418
+ }
419
+ }
420
+
421
+ /* Allow for unknown variables to pass through */
422
+ if (rawValue.match(/>passthrough/)) {
423
+ const newValues = decodeUnknown(rawValue)
424
+ // console.log('>>>> newValues', newValues)
425
+ this.update(newValues)
426
+ }
425
427
  }
428
+ })
429
+
430
+ if (DEBUG) {
431
+ console.log(`Variable process ran ${this.callCount} times`)
432
+ // console.log('FINAL Value', this.config)
433
+ // console.log(this.deep)
426
434
  }
427
435
  })
428
-
429
- if (DEBUG) {
430
- console.log(`Variable process ran ${this.callCount} times`)
431
- // console.log('FINAL Value', this.config)
432
- // console.log(this.deep)
436
+ })
437
+ .then(() => {
438
+ // console.log('this.config', this.config)
439
+ /* Final post-processing here */
440
+ if (this.mergeKeys && this.config) {
441
+ this.config = mergeByKeys(this.config, '', this.mergeKeys)
433
442
  }
443
+ return this.config
434
444
  })
435
- }).then(() => {
436
- return this.config
437
- })
438
445
  })
439
446
  }
440
447
  runFunction(variableString) {
@@ -452,7 +459,6 @@ class Configorama {
452
459
  // test for object
453
460
  const functionName = hasFunc[1]
454
461
  const rawArgs = hasFunc[2]
455
-
456
462
  // TODO @DWELLS. Loop through all raw args and parse to correct datatype
457
463
  // argument is object
458
464
  let argsToPass
@@ -462,7 +468,7 @@ class Configorama {
462
468
  } else {
463
469
  // TODO fix how commas + spaces are ned
464
470
  const splitter = splitCsv(rawArgs, ', ')
465
- // console.log('splitterz', splitter)
471
+ // console.log('splitter', splitter)
466
472
  argsToPass = formatFunctionArgs(splitter)
467
473
  }
468
474
  // console.log('argsToPass runFunction', argsToPass)
@@ -470,12 +476,10 @@ class Configorama {
470
476
  // TODO check for camelCase version. | toUpperCase messes with function name
471
477
  const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
472
478
 
473
- if (!theFunction) {
474
- throw new Error(`Function "${functionName}" not found`)
475
- }
479
+ if (!theFunction) throw new Error(`Function "${functionName}" not found`)
476
480
 
477
481
  const funcValue = theFunction(...argsToPass)
478
- // console.log('funcValuex', funcValue)
482
+ // console.log('funcValue', funcValue)
479
483
  // console.log('typeof funcValue', typeof funcValue)
480
484
  let replaceVal = funcValue
481
485
  if (typeof funcValue === 'string') {
@@ -525,23 +529,20 @@ class Configorama {
525
529
  * @returns {TerminalProperty[]} The terminal properties of the given root object, with the path
526
530
  * and value of each
527
531
  */
528
- getProperties(root, atRoot, current, cntxt, rslts) {
529
- let context = cntxt
530
- if (!context) {
531
- context = []
532
- }
533
- let results = rslts
534
- if (!results) {
535
- results = []
536
- }
532
+ getProperties(root, atRoot, current, _context, _results) {
533
+ let context = _context
534
+ if (!context) context = []
535
+ let results = _results
536
+ if (!results) results = []
537
+
537
538
  const addContext = (value, key) => {
538
539
  return this.getProperties(root, false, value, context.concat(key), results)
539
540
  }
540
- if (_.isArray(current)) {
541
- _.map(current, addContext)
542
- } else if (_.isObject(current) && !_.isDate(current) && !_.isRegExp(current) && !_.isFunction(current)) {
541
+ if (isArray(current)) {
542
+ map(current, addContext)
543
+ } else if (isObject(current) && !isDate(current) && !isRegExp(current) && !isFunction(current)) {
543
544
  if (atRoot || current !== root) {
544
- _.mapValues(current, addContext)
545
+ mapValues(current, addContext)
545
546
  }
546
547
  } else {
547
548
  // TODO Add values to leaves here
@@ -549,23 +550,22 @@ class Configorama {
549
550
  path: context,
550
551
  value: current,
551
552
  }
552
-
553
- const thePath = (leaf.path.length > 1) ? leaf.path.join('.') : leaf.path[0]
553
+ const thePath = leaf.path.length > 1 ? leaf.path.join('.') : leaf.path[0]
554
554
  let originalValue = dotProp.get(this.originalConfig, thePath)
555
555
  // TODO @DWELLS make recursive
556
556
  if (!originalValue) {
557
557
  const parentArray = leaf.path.slice(0, -1)
558
- const parentPath = (parentArray > 1) ? parentArray.join('.') : parentArray[0]
558
+ const parentPath = parentArray > 1 ? parentArray.join('.') : parentArray[0]
559
559
  originalValue = dotProp.get(this.originalConfig, parentPath)
560
560
  }
561
561
  leaf.originalSource = originalValue
562
- if (originalValue && _.isString(originalValue)) {
562
+ if (originalValue && isString(originalValue)) {
563
563
  const varString = cleanVariable(originalValue, this.variableSyntax)
564
564
  if (varString.match(fileRefSyntax)) {
565
565
  leaf.isFileRef = true
566
566
  }
567
567
  }
568
- // dotProp.get(this.originalConfig, thePath)
568
+ // dotProp.get(this.originalConfig, thePath)
569
569
  results.push(leaf)
570
570
  }
571
571
  return results
@@ -585,14 +585,14 @@ class Configorama {
585
585
  // console.log('properties', properties)
586
586
  const variables = properties.filter((property) => {
587
587
  // Initial check if value has variable string in it
588
- return _.isString(property.value) && property.value.match(this.variableSyntax)
588
+ return isString(property.value) && property.value.match(this.variableSyntax)
589
589
  })
590
- return _.map(variables, (valueObject) => {
590
+ // console.log('variables', variables)
591
+ return map(variables, (valueObject) => {
591
592
  // console.log('valueObject', valueObject)
592
- return this.populateValue(valueObject, false)
593
- .then((populated) => {
594
- return _.assign({}, valueObject, { populated: populated.value })
595
- })
593
+ return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
594
+ return assign({}, valueObject, { populated: populated.value })
595
+ })
596
596
  })
597
597
  }
598
598
  /**
@@ -602,13 +602,15 @@ class Configorama {
602
602
  * @returns {Promise<number>} resolving with the number of changes that were applied to the given
603
603
  * target
604
604
  */
605
- assignProperties(target, populations) { // eslint-disable-line class-methods-use-this
606
- return Promise.all(populations)
607
- .then((results) => results.forEach((result) => {
605
+ assignProperties(target, populations) {
606
+ // eslint-disable-line class-methods-use-this
607
+ return Promise.all(populations).then((results) => {
608
+ return results.forEach((result) => {
608
609
  if (result.value !== result.populated) {
609
- _.set(target, result.path, result.populated)
610
+ set(target, result.path, result.populated)
610
611
  }
611
- }))
612
+ })
613
+ })
612
614
  }
613
615
  /**
614
616
  * Populate the variables in the given object.
@@ -620,20 +622,19 @@ class Configorama {
620
622
  }
621
623
  populateObjectImpl(objectToPopulate) {
622
624
  this.callCount = this.callCount + 1
623
-
625
+
624
626
  if (DEBUG) {
625
- deepLog(`objectToPopulate ${this.callCount}`, objectToPopulate)
627
+ deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
628
+ // process.exit(0)
626
629
  }
627
630
 
628
631
  const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
632
+ // console.log('leaves', leaves)
629
633
  const populations = this.populateVariables(leaves)
630
-
631
634
  // console.log("FILL LEAVES", populations)
632
635
 
633
636
  if (populations.length === 0) {
634
- if (DEBUG) {
635
- console.log('Config Population Finished')
636
- }
637
+ if (DEBUG) console.log('Config Population Finished')
637
638
  return Promise.resolve(objectToPopulate)
638
639
  }
639
640
 
@@ -656,20 +657,15 @@ class Configorama {
656
657
  * @returns {Object|String|MatchResult[]} The given property or the identified matches
657
658
  */
658
659
  getMatches(property) {
659
- if (typeof property !== 'string') {
660
- return property
661
- }
660
+ if (typeof property !== 'string') return property
662
661
  const matches = property.match(this.variableSyntax)
663
-
664
- if (!matches || !matches.length) {
665
- return property
666
- }
667
- return _.map(matches, match => {
662
+ if (!matches || !matches.length) return property
663
+ return map(matches, (match) => {
668
664
  // console.log('match', match)
669
- return ({
665
+ return {
670
666
  match: match,
671
667
  variable: cleanVariable(match, this.variableSyntax),
672
- })
668
+ }
673
669
  })
674
670
  }
675
671
  /**
@@ -680,7 +676,7 @@ class Configorama {
680
676
  */
681
677
  populateMatches(matches, valueObject, root) {
682
678
  // console.log('matches', matches)
683
- return _.map(matches, (match) => {
679
+ return map(matches, (match) => {
684
680
  return this.splitAndGet(match.variable, valueObject, root)
685
681
  })
686
682
  }
@@ -699,13 +695,12 @@ class Configorama {
699
695
  let result = valueObject.value
700
696
  for (let i = 0; i < matches.length; i += 1) {
701
697
  this.warnIfNotFound(matches[i].variable, results[i])
702
- // console.log('REDNER MATCHES', results[i])
698
+ // console.log('Render MATCHES', results[i])
703
699
  let valueToPop = results[i]
704
700
  // TODO refactor this. __internal_only_flag needed to stop clash with sync/async file resolution
705
701
  if (results[i] && typeof results[i] === 'object' && results[i].__internal_only_flag) {
706
702
  valueToPop = results[i].value
707
703
  }
708
-
709
704
  result = this.populateVariable(valueObject, matches[i].match, valueToPop)
710
705
  /*
711
706
  console.log('> valueToPop', valueToPop)
@@ -719,35 +714,32 @@ class Configorama {
719
714
  /**
720
715
  * Populate the given value, recursively if root is true
721
716
  * @param valueObject The value to populate variables within
722
- * @param root Whether the caller is the root populator and thereby whether to recursively
717
+ * @param root Whether the caller is the root populater and thereby whether to recursively
723
718
  * populate
724
719
  * @returns {PromiseLike<T>} A promise that resolves to the populated value, recursively if root
725
720
  * is true
726
721
  */
727
- populateValue(valueObject, root) {
722
+ populateValue(valueObject, root, caller) {
728
723
  if (DEBUG) {
729
724
  console.log('─────────────────────────────────────────────▶')
730
- console.log('>>>>>>>> populateValue')
725
+ console.log('>>>>>>>> populateValue', caller)
731
726
  console.log(valueObject)
732
727
  }
733
728
  const property = valueObject.value
734
729
  const matches = this.getMatches(property)
735
730
  /*
736
- console.log('matchesmatches', matches)
731
+ console.log('populateValue matches', matches)
737
732
  /** */
738
- if (!_.isArray(matches)) {
733
+ if (!isArray(matches)) {
739
734
  return Promise.resolve(property)
740
735
  }
741
736
  const populations = this.populateMatches(matches, valueObject, root)
742
737
  return Promise.all(populations)
743
- .then(results => this.renderMatches(valueObject, matches, results))
738
+ .then((results) => this.renderMatches(valueObject, matches, results))
744
739
  .then((result) => {
745
- /*
746
- console.log('renderMatches result', result)
747
- /** */
748
-
749
- if (root && matches.length) {
750
- return this.populateValue({ value: result.value }, root)
740
+ // console.log('renderMatches result', result)
741
+ if (root && isArray(matches)) {
742
+ return this.populateValue({ value: result.value }, root, 'self populateValue')
751
743
  }
752
744
  return result
753
745
  })
@@ -766,7 +758,7 @@ class Configorama {
766
758
  /**
767
759
  * Split the cleaned variable string containing one or more comma delimited variables and get a
768
760
  * final value for the entirety of the string
769
- * @param varible The variable string to split and get a final value for
761
+ * @param variable The variable string to split and get a final value for
770
762
  * @param property The original property string the given variable was extracted from
771
763
  * @returns {Promise} A promise resolving to the final value of the given variable
772
764
  */
@@ -776,29 +768,26 @@ class Configorama {
776
768
  console.log('valueObject', valueObject)
777
769
  console.log('root', root)
778
770
  }
779
-
780
771
  /* requires node 8.11+
781
772
  if (valueObject.value.match(/(?<!^)> function /)) {
782
773
  // valueObject.value = valueObject.value.replace(/(?<!^)> function /, '')
783
774
  // valueObject.value = valueObject.value.replace(/^> function /, '')
784
775
  // valueObject.value = `> function ${valueObject.value}`
785
- }
786
- */
776
+ }*/
787
777
 
788
778
  const parts = splitByComma(variable)
789
-
790
- if (parts.length > 1) {
791
- if (DEBUG) {
792
- console.log('parts', parts)
793
- console.log('parts variable:', variable)
794
- console.log('parts property:', valueObject.value)
795
- console.log('All parts:', parts)
796
- console.log('-----')
797
- }
798
- return this.overwrite(parts, valueObject)
779
+ if (DEBUG) {
780
+ console.log('parts', parts)
781
+ console.log('parts variable:', variable)
782
+ console.log('parts property:', valueObject.value)
783
+ console.log('All parts:', parts)
784
+ console.log('-----')
799
785
  }
800
-
801
- return this.getValueFromSource(parts[0], valueObject, 'splitAndGet')
786
+ if (parts.length <= 1) {
787
+ return this.getValueFromSource(parts[0], valueObject, 'splitAndGet')
788
+ }
789
+ // More than 2 parts, so we need to overwrite
790
+ return this.overwrite(parts, valueObject)
802
791
  }
803
792
  /**
804
793
  * Populate a given property, given the matched string to replace and the value to replace the
@@ -811,35 +800,54 @@ class Configorama {
811
800
  */
812
801
  populateVariable(valueObject, matchedString, valueToPopulate) {
813
802
  let property = valueObject.value
803
+ // console.log('init property', property)
804
+ let DEBUG_TYPE = false
814
805
  if (DEBUG) {
815
- console.log('────────START populateVariable──────────────')
816
- console.log('populateVariable: valueToPopulate', valueToPopulate)
817
- console.log('populateVariable: valueObject', valueObject)
818
- console.log('populateVariable: property', property)
819
- console.log('populateVariable: matchedString', matchedString)
806
+ console.log('────────START populateVar──────────────')
807
+ console.log('populateVar: valueToPopulate', valueToPopulate)
808
+ console.log('populateVar: typeof valueToPopulate', typeof valueToPopulate)
809
+ console.log(`populateVar: path "${valueObject.path}"`)
810
+ console.log(`populateVar: value \`${valueObject.value}\``)
811
+ console.log(`populateVar: originalSource \`${valueObject.originalSource}\``)
812
+ console.log('populateVar: property', property)
813
+ console.log('populateVar: matchedString', matchedString)
814
+ }
815
+
816
+ const originalSrc = valueObject.originalSource || ''
817
+ const hasFilters = originalSrc.match(this.filterMatch)
818
+ let foundFilters = []
819
+ if (hasFilters) {
820
+ foundFilters = hasFilters[1]
821
+ .split('|')
822
+ .map((filter) => filter.trim())
823
+ .filter(Boolean)
820
824
  }
825
+ // console.log('foundFilters', foundFilters)
821
826
 
822
827
  // total replacement
823
828
  if (property === matchedString) {
829
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE total replacement')
824
830
  const v = valueObject.value || ''
825
- const originalSrc = valueObject.originalSource || ''
826
831
  property = valueToPopulate
832
+
827
833
  /* Handle ${self:custom.ref, ''} with deep values */
828
834
  if (v.match(deepRefSyntax) && originalSrc.match(this.variableSyntax) && !v.match(/deep\:(\d*)\..*}$/)) {
829
- // console.log('MAJAJAAJJAAJAJJAJA', this.deep)
835
+ // console.log('deep var', this.deep)
830
836
  // console.log('originalSrc', originalSrc)
831
837
  // console.log('value', v)
832
- let deepIndex = Number(v.match(/deep\:(\d*)/)[1])
838
+ let deepIndex = Number(v.match(deepIndexPattern)[1])
833
839
  let item = this.deep[deepIndex]
834
-
840
+
835
841
  if (item.match(deepRefSyntax)) {
836
- deepIndex = Number(item.match(/deep\:(\d*)/)[1])
842
+ deepIndex = Number(item.match(deepIndexPattern)[1])
837
843
  item = this.deep[deepIndex]
838
844
  }
839
845
  property = this.deep[deepIndex]
846
+ // console.log('NEW PROPERTY after deep ref', property)
840
847
  }
841
848
  // partial replacement, string
842
- } else if (_.isString(valueToPopulate)) {
849
+ } else if (isString(valueToPopulate)) {
850
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE isString')
843
851
  // if (property.match(/^> function /g)) {
844
852
  //
845
853
  // const innerFunc = /> function (\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
@@ -849,7 +857,6 @@ class Configorama {
849
857
  // console.log('xxxx', rep)
850
858
  // console.log('valueToPopulate', valueToPopulate)
851
859
  // }
852
-
853
860
 
854
861
  let currentMatchedString = matchedString
855
862
  /* Address fall through values if found */
@@ -859,7 +866,6 @@ class Configorama {
859
866
  currentMatchedString = valueObject.value
860
867
  }
861
868
  }
862
-
863
869
  /*
864
870
  console.log('>------')
865
871
  console.log('isString og matchedString', matchedString)
@@ -870,28 +876,56 @@ class Configorama {
870
876
  console.log('isString currentMatchedString', currentMatchedString)
871
877
  console.log('>------')
872
878
  /** */
873
-
874
- // (replaceThis, withThis, inThis)
875
- property = replaceall(currentMatchedString, valueToPopulate, property)
876
- // console.log('property', property)
879
+ // Handle comma ${opt:stage, dev} and remove extra }
880
+ if (
881
+ currentMatchedString.match(this.variableSyntax) &&
882
+ !valueToPopulate.match(this.variableSyntax) &&
883
+ valueToPopulate.match(/}$/)
884
+ ) {
885
+ valueToPopulate = valueToPopulate.replace(/}$/, '')
886
+ }
887
+
888
+ property = replaceAll(currentMatchedString, valueToPopulate, property)
889
+ // console.log('property replaceAll', property)
877
890
 
878
891
  // if (property.match(/^> function /g)) {
879
892
  // console.log('REPLACE after', property)
880
893
  // }
881
-
894
+
882
895
  // partial replacement, number
883
- } else if (_.isNumber(valueToPopulate)) {
884
- property = replaceall(matchedString, String(valueToPopulate), property)
885
- // TODO This was temp fix for array value mismatch from filters. This fixes filterInner: ${commas | split(${self:inner}, 2) }
886
- // } else if (_.isArray(valueToPopulate) && valueToPopulate.length === 1) {
887
- // property = replaceall(matchedString, String(valueToPopulate[0]), property)
888
- } else if (_.isObject(valueToPopulate)) {
889
- // console.log('OBJECT MATCH', valueToPopulate)
890
- property = replaceall(matchedString, JSON.stringify(valueToPopulate), property)// .replace(/}$/, '').replace(/^\$\{/, '')
896
+ } else if (isNumber(valueToPopulate)) {
897
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE isNumber')
898
+ property = replaceAll(matchedString, String(valueToPopulate), property)
899
+ // TODO This was temp fix for array value mismatch from filters. This fixes filterInner: ${commas | split(${self:inner}, 2) }
900
+ // } else if (isArray(valueToPopulate) && valueToPopulate.length === 1) {
901
+ // property = replaceAll(matchedString, String(valueToPopulate[0]), property)
902
+ } else if (isObject(valueToPopulate)) {
903
+ if (DEBUG_TYPE) console.log('DEBUG_TYPEisObject')
904
+
905
+ const objStr = JSON.stringify(valueToPopulate)
906
+ /* Check if variable inside another variable. E.g. ${env:${self:someObject}} that resolves to ${env:{...}} */
907
+ if (
908
+ property.trim() !== matchedString.trim() &&
909
+ property.indexOf(matchedString) !== -1 &&
910
+ matchedString.match(this.variableSyntax) &&
911
+ property.match(this.variableSyntax)
912
+ ) {
913
+ const isVar = /^\${[a-zA-Z0-9_]+:/.test(property)
914
+ if (isVar) {
915
+ // console.log('INSIDE', property, matchedString)
916
+ // console.log('isVar', isVar)
917
+ throw new Error(
918
+ `Invalid variable syntax "${property}" resolves to "${replaceAll(matchedString, objStr, property)}"`,
919
+ )
920
+ }
921
+ }
922
+ // console.log('OBJECT MATCH', `"${objStr}"`)
923
+ property = replaceAll(matchedString, objStr, property) // .replace(/}$/, '').replace(/^\$\{/, '')
891
924
  // console.log('property', property)
892
925
  // TODO run functions here
893
- // console.log('othere new propetry', property)
926
+ // console.log('other new prop', property)
894
927
  } else {
928
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE else')
895
929
  let missingValue = matchedString
896
930
 
897
931
  if (matchedString.match(deepRefSyntax)) {
@@ -917,18 +951,15 @@ class Configorama {
917
951
  originalSource: valueObject.originalSource,
918
952
  // set __internal_only_flag to note this is object we make not a resolved value
919
953
  __internal_only_flag: true,
920
- caller: 'nestedVar'
954
+ caller: 'nestedVar',
921
955
  }
922
956
  }
923
957
 
924
958
  const errorMessage = `
925
959
  Missing Value ${missingValue} - ${matchedString}
926
-
927
- Make sure the property is being passed in correctly
928
-
929
- For variable:
930
-
931
- ${valueObject.path}: ${valueObject.originalSource}
960
+ \nMake sure the property is being passed in correctly
961
+ \nFor variable:
962
+ \n${valueObject.path}: ${valueObject.originalSource}
932
963
  `
933
964
  throw new Error(errorMessage)
934
965
  }
@@ -938,7 +969,7 @@ ${valueObject.path}: ${valueObject.originalSource}
938
969
  const prop = cleanVariable(property, this.variableSyntax)
939
970
  // console.log('prop', prop)
940
971
  if (property.match(/^> function /g) && prop) {
941
- // console.log('PRPOpropertyproperty', property)
972
+ // console.log('func prop', property)
942
973
  // console.log('Prop', prop)
943
974
  }
944
975
  const func = funcRegex.exec(property)
@@ -946,10 +977,10 @@ ${valueObject.path}: ${valueObject.originalSource}
946
977
  if (func && property.match(/^> function /g)) {
947
978
  /* IMPORTANT fix `finalProp` for nested function reference
948
979
  nestedOne: 'hi'
949
- nestedTwo: ${merge('haha', 'wowowow')}
980
+ nestedTwo: ${merge('nice', 'wow')}
950
981
  mergeNested: ${merge('lol', ${nestedTwo})}
951
982
  */
952
- const finalProp = (property.match(/(?<!^)> function /)) ? prop : property
983
+ const finalProp = property.match(/(?<!^)> function /) ? prop : property
953
984
 
954
985
  return {
955
986
  value: finalProp, // prop to fix nested ¯\_(ツ)_/¯
@@ -989,17 +1020,44 @@ ${valueObject.path}: ${valueObject.originalSource}
989
1020
  // property = newer
990
1021
  // }
991
1022
  }
992
- // console.log('XXXX property', property)
1023
+
1024
+ // console.log('foundFilters', foundFilters)
1025
+
1026
+ /* Apply filters if found */
1027
+ //console.log('> property', property)
1028
+ if (
1029
+ foundFilters.length > 0 &&
1030
+ typeof valueToPopulate === 'string' &&
1031
+ !valueToPopulate.match(deepRefSyntax) &&
1032
+ !property.match(this.variableSyntax)
1033
+ ) {
1034
+ // If filter cache exists we need to remove filter that have already been run
1035
+ if (this.filterCache[valueObject.path]) {
1036
+ foundFilters = foundFilters.filter((filter) => {
1037
+ return !this.filterCache[valueObject.path].includes(filter)
1038
+ })
1039
+ }
1040
+ property = foundFilters.reduce((acc, filter) => {
1041
+ const newVal = this.filters[filter](acc, 'from populateVariable')
1042
+ // console.log('PROPERTY', newVal)
1043
+ return newVal
1044
+ }, property)
1045
+ this.filterCache[valueObject.path] = (this.filterCache[valueObject.path] || []).concat(foundFilters)
1046
+ // console.log('NEW PROPERTY', property)
1047
+ // console.log('typeof property', typeof property)
1048
+ }
1049
+ // console.log('filterCache', this.filterCache)
1050
+ // console.log('XXXX property', typeof property)
993
1051
  // console.log('XXXX path', valueObject.path)
994
1052
  // console.log('XXXX originalSource', valueObject.originalSource)
1053
+ // console.log('end property', property)
995
1054
  return {
996
1055
  value: property,
997
1056
  path: valueObject.path,
998
1057
  originalSource: valueObject.originalSource,
999
- // set __internal_only_flag to note this is object we make not a resolved value
1000
- __internal_only_flag: true,
1058
+ __internal_only_flag: true, // set __internal_only_flag to note this is object we make not a resolved value
1001
1059
  caller: 'end',
1002
- count: this.callCount
1060
+ count: this.callCount,
1003
1061
  }
1004
1062
  }
1005
1063
  // ###############
@@ -1015,21 +1073,18 @@ ${valueObject.path}: ${valueObject.originalSource}
1015
1073
  overwrite(variableStrings, valueObject) {
1016
1074
  const propertyString = valueObject.value
1017
1075
  // console.log('variableStrings', variableStrings)
1018
-
1019
1076
  // console.log('propertyString', typeof propertyString)
1020
1077
  const variableValues = variableStrings.map((variableString) => {
1021
1078
  // This runs on nested variable resolution
1022
1079
  return this.getValueFromSource(variableString, valueObject, 'overwrite')
1023
1080
  })
1024
-
1025
1081
  // console.log('variableValues', variableValues)
1026
-
1027
- return Promise.all(variableValues).then(values => {
1082
+ return Promise.all(variableValues).then((values) => {
1028
1083
  let deepPropertyStr = propertyString
1029
1084
  let deepProperties = 0
1030
1085
  values.forEach((value, index) => {
1031
- // console.log('valuevaluevaluevaluevaluevaluevaluevalue', value)
1032
- if (_.isString(value) && value.match(this.variableSyntax)) {
1086
+ // console.log('───────────────────────────────> value', value)
1087
+ if (isString(value) && value.match(this.variableSyntax)) {
1033
1088
  deepProperties += 1
1034
1089
  // console.log('makeDeepVariable overwrite', value)
1035
1090
  const deepVariable = this.makeDeepVariable(value)
@@ -1041,19 +1096,20 @@ ${valueObject.path}: ${valueObject.originalSource}
1041
1096
  })
1042
1097
  return deepProperties > 0
1043
1098
  ? Promise.resolve(deepPropertyStr) // return deep variable replacement of original
1044
- : Promise.resolve(values.find(isValidValue))// resolve first valid value, else undefined
1099
+ : Promise.resolve(values.find(isValidValue)) // resolve first valid value, else undefined
1045
1100
  })
1046
1101
  }
1047
-
1048
1102
  /**
1049
1103
  * Given any variable string, return the value it should be populated with.
1050
1104
  * @param variableString The variable string to retrieve a value for.
1051
1105
  * @returns {Promise.<TResult>|*} A promise resolving to the given variables value.
1052
1106
  */
1053
1107
  getValueFromSource(variableString, valueObject, caller) {
1108
+ // console.log('getValueFromSource caller', caller)
1054
1109
  const propertyString = valueObject.value
1055
1110
  const pathValue = valueObject.path
1056
- // return resolved value
1111
+ // console.log('getValueFromSource propertyString', propertyString)
1112
+ // console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
1057
1113
  if (this.tracker.contains(variableString)) {
1058
1114
  // console.log('try to get', variableString)
1059
1115
  return this.tracker.get(variableString, propertyString)
@@ -1071,29 +1127,39 @@ ${valueObject.path}: ${valueObject.originalSource}
1071
1127
  }
1072
1128
 
1073
1129
  const filters = propertyString.match(/\s\|/)
1130
+ let promiseKey
1074
1131
  // TODO match () or pipes |
1075
1132
  if (filters) {
1133
+ const string = cleanVariable(propertyString, this.variableSyntax, true)
1134
+ // console.log('string', string)
1135
+ const deeperValue = getTextAfterOccurance(string, variableString)
1136
+ // console.log('deeperValue', deeperValue)
1137
+ // console.log('filters', filters)
1076
1138
  // console.log('variableString', variableString)
1139
+ promiseKey = deeperValue.match(/\s\|/) ? deeperValue : undefined
1077
1140
 
1078
1141
  // TODO clean this up
1079
1142
  const t = variableString.split('|')
1143
+ // console.log('variableString', variableString)
1144
+ // console.log('valueObject', valueObject)
1145
+ // console.log('t', t)
1146
+ const _filter = string
1147
+ .split('|')
1148
+ .filter((value, index, arr) => {
1149
+ return index > 0
1150
+ })
1151
+ .map((f) => {
1152
+ return trim(f)
1153
+ })
1154
+ // console.log('filters to run', _filter)
1080
1155
 
1081
- const string = cleanVariable(propertyString, this.variableSyntax, true)
1082
- const filterz = string.split('|').filter((value, index, arr) => {
1083
- return index > 0
1084
- }).map((f) => {
1085
- return _.trim(f)
1086
- })
1087
- // console.log('filters to run', filterz)
1088
-
1089
- newHasFilter = filterz
1156
+ newHasFilter = _filter
1090
1157
  // If current variable string has no pipes, it has no filters
1091
1158
  if (!variableString.match(/\|/)) {
1092
1159
  newHasFilter = null
1093
1160
  }
1094
- // console.log('HAS FILTERS', filters)
1095
- // console.log('t', t)
1096
- variableString = _.trim(t[0])
1161
+ // console.log('newHasFilter', newHasFilter)
1162
+ variableString = trim(t[0])
1097
1163
  }
1098
1164
 
1099
1165
  let resolverFunction
@@ -1113,21 +1179,17 @@ ${valueObject.path}: ${valueObject.originalSource}
1113
1179
  }
1114
1180
  return false
1115
1181
  })
1116
-
1117
1182
  // console.log('found', found)
1118
1183
 
1119
1184
  if (found && resolverFunction) {
1120
1185
  // TODO finalize resolverFunction API
1121
- const valuePromise = resolverFunction(
1122
- variableString,
1123
- this.options,
1124
- this.config,
1125
- valueObject
1126
- ).then((val) => {
1186
+ const valuePromise = resolverFunction(variableString, this.options, this.config, valueObject).then((val) => {
1127
1187
  // console.log('VALUE', val)
1128
- if (val === null || typeof val === 'undefined' ||
1188
+ if (
1189
+ val === null ||
1190
+ typeof val === 'undefined' ||
1129
1191
  /* match deep refs as empty {}, they need resolving via functions */
1130
- (typeof val === 'object' && _.isEmpty(val) && variableString.match(/deep\:/))
1192
+ (typeof val === 'object' && isEmpty(val) && variableString.match(/deep\:/))
1131
1193
  ) {
1132
1194
  // console.log('variableString', variableString)
1133
1195
  const cleanV = cleanVariable(propertyString, this.variableSyntax)
@@ -1135,9 +1197,8 @@ ${valueObject.path}: ${valueObject.originalSource}
1135
1197
 
1136
1198
  if (variableString.match(/deep\:/)) {
1137
1199
  // return Promise.resolve(this.getValueFromDeep(variableString))
1138
- const deepIndex = variableString.match(/deep\:(\d*)/)
1139
- const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g)
1140
- const deepRef = variableString.replace(deepPrefixReplace, '')
1200
+ const deepIndex = variableString.match(deepIndexPattern)
1201
+ const deepRef = variableString.replace(deepPrefixReplacePattern, '')
1141
1202
  // console.log('deepRef', deepRef)
1142
1203
  // console.log('deepIndexMatch', deepIndex)
1143
1204
  if (deepIndex[1] && this.deep.length) {
@@ -1152,22 +1213,21 @@ ${valueObject.path}: ${valueObject.originalSource}
1152
1213
  // TODO throw on empty values?
1153
1214
  // No fallback value found AND this is undefined, throw error
1154
1215
  if (valueCount.length === 1) {
1155
- throw new Error(`Error: Variable not found.
1156
- Variable: "${variableString}" from ${propertyString}
1157
- Value Path: ${(valueObject.path) ? valueObject.path.join('.') : 'na'}
1158
- Original Value: ${valueObject.originalSource}
1159
- Please fix this reference or provide a valid fallback value.
1160
- Like so: \${${variableString}, "fallbackValue"\}.`)
1216
+ throw new Error(`
1217
+ Unable to resolve variable ${propertyString} from "${valueObject.originalSource}" at path ${valueObject.path ? `"${arrayToJsonPath(valueObject.path)}"` : 'na'}
1218
+ \nFix this reference, your inputs and/or provide a valid fallback value.
1219
+ \nExample of setting a fallback value: \${${variableString}, "fallbackValue"\}\n`)
1161
1220
  }
1162
-
1163
1221
  // no value resolved but fallback value exists, keep moving on
1164
1222
  return Promise.resolve(val)
1165
1223
  }
1224
+ /*
1166
1225
  // console.log('------')
1167
1226
  // console.log('propertyString', propertyString)
1168
1227
  // console.log('resolved val', val)
1169
1228
  // console.log('------')
1170
1229
  // console.log('newHasFilter', newHasFilter)
1230
+ /** */
1171
1231
  // No filters found. return value
1172
1232
  if (!newHasFilter) {
1173
1233
  return Promise.resolve(val)
@@ -1179,47 +1239,50 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1179
1239
  }
1180
1240
  return acc.concat({
1181
1241
  filter: this.filters[currentFilter],
1242
+ filterName: currentFilter,
1182
1243
  // args: argsToPass
1183
1244
  })
1184
1245
  }, [])
1246
+ // console.log('newUse', newUse)
1185
1247
 
1186
1248
  if (typeof val === 'string' && val.match(/deep:/)) {
1187
1249
  // TODO refactor the deep filter logic here. match | filter | filter..
1188
- const allFilters = propertyString.replace(/}$/, '').split('|').reduce((acc, currentFilter, i) => {
1189
- if (i === 0) {
1250
+ const allFilters = propertyString
1251
+ .replace(/}$/, '')
1252
+ .split('|')
1253
+ .reduce((acc, currentFilter, i) => {
1254
+ if (i === 0) {
1255
+ return acc
1256
+ }
1257
+ acc += `| ${trim(currentFilter)}`
1190
1258
  return acc
1191
- }
1192
- acc += `| ${_.trim(currentFilter)}`
1193
- return acc
1194
- }, '')
1195
-
1259
+ }, '')
1196
1260
  // add filters to deep references if filter is used
1197
- const deepValueWithFilters = (newHasFilter[1]) ? val.replace(/}$/, ` ${allFilters}}`) : val
1261
+ const deepValueWithFilters = newHasFilter[1] ? val.replace(/}$/, ` ${allFilters}}`) : val
1198
1262
  // console.log('deepValueWithFilters', deepValueWithFilters)
1199
1263
  return Promise.resolve(deepValueWithFilters)
1200
1264
  }
1201
-
1202
- // console.log('newUse', newUse)
1203
1265
  /* Loop over filters used and produce new value */
1204
1266
  const newValue = newUse.reduce((a, c) => {
1205
1267
  // Fix for async value resolution. That code file refs returns object with .value
1206
- const theValue = (typeof a === 'object' && a.__internal_only_flag) ? a.value : a
1207
-
1208
- if (typeof c.filter === 'function') {
1209
- if (c.args) {
1210
- return c.filter(theValue, ...c.args)
1211
- }
1212
- return c.filter(theValue)
1268
+ const theValue = typeof a === 'object' && a.__internal_only_flag ? a.value : a
1269
+ if (typeof c.filter !== 'function') {
1270
+ return theValue
1271
+ }
1272
+ if (c.args) {
1273
+ this.filterCache[pathValue] = (this.filterCache[pathValue] || []).concat(c.filterName)
1274
+ return c.filter(theValue, ...c.args, 'from getValueFromSource with args')
1213
1275
  }
1214
- return theValue
1276
+ this.filterCache[pathValue] = (this.filterCache[pathValue] || []).concat(c.filterName)
1277
+ return c.filter(theValue, 'from getValueFromSource')
1215
1278
  }, val)
1279
+ // console.log('newValue', newValue)
1216
1280
 
1217
1281
  return Promise.resolve(newValue)
1218
1282
  })
1219
-
1283
+ // console.log('newHasFilter', newHasFilter)
1220
1284
  // TODO do something with func here?
1221
-
1222
- return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter)
1285
+ return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter, promiseKey)
1223
1286
  }
1224
1287
 
1225
1288
  /* fall through case with self refs */
@@ -1253,7 +1316,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1253
1316
  }
1254
1317
  const fallbackStr = getFallbackString(split, nestedVar)
1255
1318
  return this.getValueFromSource(variableString, {
1256
- value: fallbackStr
1319
+ value: fallbackStr,
1257
1320
  }, 'nestedVar')
1258
1321
  }
1259
1322
 
@@ -1265,11 +1328,11 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1265
1328
  if (fallbackValue) {
1266
1329
  // recurse on fallback and check again
1267
1330
  return this.getValueFromSource(`${variableString})`, {
1268
- value: propertyString
1331
+ value: propertyString,
1269
1332
  }, 'cleanClean.match(fileRefSyntax)')
1270
1333
  }
1271
1334
  }
1272
- // const fallbackValue = split[1] || split[0]
1335
+ // const fallbackValue = split[1]
1273
1336
  // console.log('variableString', variableString)
1274
1337
  // console.log('propertyString', propertyString)
1275
1338
  // console.log('fallbackValue', fallbackValue)
@@ -1281,20 +1344,23 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1281
1344
 
1282
1345
  // has fallback but needs deeper lookup. Call getValueFromSource again
1283
1346
  if (fallbackValue) {
1284
- if (DEBUG) {
1285
- console.log('fallbackValue', fallbackValue)
1286
- }
1347
+ if (DEBUG) console.log('fallbackValue', fallbackValue)
1348
+ // console.log('fallbackValue', fallbackValue)
1287
1349
  // recurse on fallback and check again
1288
- return this.getValueFromSource(fallbackValue, {
1289
- value: propertyString
1290
- }, 'fallbackValue')
1350
+ return this.getValueFromSource(
1351
+ fallbackValue,
1352
+ {
1353
+ value: propertyString,
1354
+ },
1355
+ 'fallbackValue',
1356
+ )
1291
1357
  }
1292
1358
  }
1293
1359
 
1294
1360
  // Variable NOT FOUND. Warn user
1295
1361
  const errorMessage = [
1296
1362
  `Invalid variable reference syntax`,
1297
- `Key: "${(valueObject.path) ? valueObject.path.join('.') : 'na'}"`,
1363
+ `Key: "${valueObject.path ? valueObject.path.join('.') : 'na'}"`,
1298
1364
  `Variable: "${variableString}" from ${propertyString} not found`,
1299
1365
  ]
1300
1366
 
@@ -1325,28 +1391,21 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1325
1391
 
1326
1392
  return this.tracker.add(variableString, notFoundPromise, propertyString, newHasFilter)
1327
1393
  }
1328
- getValueFromString(variableString) {
1329
- const valueToPopulate = variableString.replace(/^['"]|['"]$/g, '')
1330
- return Promise.resolve(valueToPopulate)
1331
- }
1332
-
1333
1394
  getValueFromSelf(variableString, o, x, data) {
1334
1395
  /*
1335
1396
  console.log('getValueFromSelf variableString', variableString)
1336
1397
  /** */
1337
-
1338
1398
  // console.log('self data', data)
1339
-
1340
1399
  const split = variableString.split(':')
1341
- const variable = (split.length && split[1]) ? split[1] : variableString
1400
+ const variable = split.length && split[1] ? split[1] : variableString
1342
1401
  const valueToPopulate = this.config
1343
- let deepProperties = variable.split('.').filter(property => property)
1402
+ let deepProperties = variable.split('.').filter((property) => property)
1344
1403
  // console.log('self deep', deepProperties)
1345
1404
  // console.log('self valueToPopulate', valueToPopulate)
1346
1405
 
1347
1406
  /* its file ref so we need to shift lookup for self in nested files */
1348
1407
  if (data.isFileRef) {
1349
- const dotPropPath = (deepProperties.length > 1) ? deepProperties.join('.') : deepProperties[0]
1408
+ const dotPropPath = deepProperties.length > 1 ? deepProperties.join('.') : deepProperties[0]
1350
1409
  const exists = dotProp.get(valueToPopulate, dotPropPath)
1351
1410
  // console.log('self exists', exists)
1352
1411
  if (!exists) {
@@ -1355,7 +1414,6 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1355
1414
  // console.log('self fixed deepProperties', deepProperties)
1356
1415
  }
1357
1416
  }
1358
-
1359
1417
  return this.getDeeperValue(deepProperties, valueToPopulate).then((res) => {
1360
1418
  /*
1361
1419
  console.log('self getDeeperValue variableString', variableString)
@@ -1364,7 +1422,6 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1364
1422
  return res
1365
1423
  })
1366
1424
  }
1367
-
1368
1425
  getValueFromFile(variableString) {
1369
1426
  // console.log('From file', `"${variableString}"`)
1370
1427
  let matchedFileString = variableString.match(fileRefSyntax)[0]
@@ -1378,7 +1435,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1378
1435
  if (hasParams) {
1379
1436
  const splitter = splitCsv(hasParams[2])
1380
1437
  const argsFound = splitter.map((arg) => {
1381
- const cleanArg = _.trim(arg).replace(/^'|"/, '').replace(/'|"$/, '')
1438
+ const cleanArg = trim(arg).replace(/^'|"/, '').replace(/'|"$/, '')
1382
1439
  return cleanArg
1383
1440
  })
1384
1441
 
@@ -1392,12 +1449,11 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1392
1449
  }
1393
1450
  // console.log('argsToPass', argsToPass)
1394
1451
 
1395
- const relativePath = trimSurroundingQuotes(matchedFileString
1396
- .replace(fileRefSyntax, (match, varName) => varName.trim())
1397
- .replace('~', os.homedir()))
1398
-
1452
+ const relativePath = trimSurroundingQuotes(
1453
+ matchedFileString.replace(fileRefSyntax, (match, varName) => varName.trim()).replace('~', os.homedir()),
1454
+ )
1399
1455
 
1400
- let fullFilePath = (path.isAbsolute(relativePath) ? relativePath : path.join(this.configPath, relativePath))
1456
+ let fullFilePath = path.isAbsolute(relativePath) ? relativePath : path.join(this.configPath, relativePath)
1401
1457
 
1402
1458
  // console.log('fullFilePath', fullFilePath)
1403
1459
 
@@ -1405,7 +1461,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1405
1461
  // Get real path to handle potential symlinks (but don't fatal error)
1406
1462
  fullFilePath = fs.realpathSync(fullFilePath)
1407
1463
 
1408
- // Only match files that are relative
1464
+ // Only match files that are relative
1409
1465
  } else if (relativePath.match(/\.\//)) {
1410
1466
  // TODO test higher parent refs
1411
1467
  const cleanName = path.basename(relativePath)
@@ -1471,17 +1527,16 @@ Check if your javascript is exporting a function that returns a value.`
1471
1527
  deepProperties.splice(0, 1)
1472
1528
  // Trim prop keys for starting/trailing spaces
1473
1529
  deepProperties = deepProperties.map((prop) => {
1474
- return _.trim(prop)
1530
+ return trim(prop)
1475
1531
  })
1476
- return this.getDeeperValue(deepProperties, valueToPopulateResolved)
1477
- .then((deepValueToPopulateResolved) => {
1478
- if (typeof deepValueToPopulateResolved === 'undefined') {
1479
- const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
1532
+ return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
1533
+ if (typeof deepValueToPopulateResolved === 'undefined') {
1534
+ const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
1480
1535
  Check if your javascript is returning the correct data.`
1481
- return Promise.reject(new Error(errorMessage))
1482
- }
1483
- return Promise.resolve(deepValueToPopulateResolved)
1484
- })
1536
+ return Promise.reject(new Error(errorMessage))
1537
+ }
1538
+ return Promise.resolve(deepValueToPopulateResolved)
1539
+ })
1485
1540
  })
1486
1541
  }
1487
1542
 
@@ -1527,13 +1582,9 @@ Please use ":" to reference sub properties`
1527
1582
  }
1528
1583
  return Promise.resolve(valueToPopulate)
1529
1584
  }
1530
-
1531
- getDeepIndex(variableString) {
1532
- const deepIndexReplace = RegExp(/^deep:|(\.[^}]+)*$/g)
1533
- return variableString.replace(deepIndexReplace, '')
1534
- }
1535
1585
  getVariableFromDeep(variableString) {
1536
- const index = this.getDeepIndex(variableString)
1586
+ const index = variableString.replace(deepIndexReplacePattern, '')
1587
+ // const index = this.getDeepIndex(variableString)
1537
1588
  /*
1538
1589
  console.log('FIND INDEX', index)
1539
1590
  console.log(this.deep)
@@ -1541,20 +1592,20 @@ Please use ":" to reference sub properties`
1541
1592
  return this.deep[index]
1542
1593
  }
1543
1594
  getValueFromDeep(variableString) {
1544
- const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g)
1545
1595
  const variable = this.getVariableFromDeep(variableString)
1546
- const deepRef = variableString.replace(deepPrefixReplace, '')
1596
+ const deepRef = variableString.replace(deepPrefixReplacePattern, '')
1547
1597
  /*
1548
1598
  console.log("GET getValueFromDeep", variableString)
1549
1599
  console.log('deepRef', deepRef)
1550
- console.log('variablex', variable)
1600
+ console.log('getValueFromDeep variable', variable)
1551
1601
  /** */
1552
- let ret = this.populateValue({ value: variable })
1602
+ let ret = this.populateValue({ value: variable }, undefined, 'getValueFromDeep')
1553
1603
  // console.log('variable ret', ret)
1554
- if (deepRef.length) { // if there is a deep reference remaining
1604
+ if (deepRef.length) {
1605
+ // if there is a deep reference remaining
1555
1606
  ret = ret.then((result) => {
1556
1607
  // console.log('DEEP RESULT', result)
1557
- if (_.isString(result.value) && result.value.match(this.variableSyntax)) {
1608
+ if (isString(result.value) && result.value.match(this.variableSyntax)) {
1558
1609
  // console.log('makeDeepVariable getValueFromDeep', result.value)
1559
1610
  const deepVariable = this.makeDeepVariable(result.value)
1560
1611
  return Promise.resolve(appendDeepVariable(deepVariable, deepRef))
@@ -1564,7 +1615,6 @@ Please use ":" to reference sub properties`
1564
1615
  }
1565
1616
  return ret
1566
1617
  }
1567
-
1568
1618
  makeDeepVariable(variable) {
1569
1619
  // console.log('MAKE DEEP', variable)
1570
1620
  let index = this.deep.findIndex((item) => variable === item)
@@ -1581,7 +1631,6 @@ Please use ":" to reference sub properties`
1581
1631
 
1582
1632
  return deepVar
1583
1633
  }
1584
-
1585
1634
  /**
1586
1635
  * Get a value that is within the given valueToPopulate. The deepProperties specify what value
1587
1636
  * to retrieve from the given valueToPopulate. The trouble is that anywhere along this chain a
@@ -1601,16 +1650,17 @@ Please use ":" to reference sub properties`
1601
1650
  console.log('deepProperties', deepProperties)
1602
1651
  console.log('valueToPopulate', valueToPopulate)
1603
1652
  /** */
1604
-
1605
1653
  const veryDeep = deepProperties.reduce(async (reducedValueParam, subProperty) => {
1606
1654
  let reducedValue = await reducedValueParam
1607
1655
  // console.log('reducedValue', reducedValue)
1608
1656
  // console.log(typeof reducedValue)
1609
1657
  // console.log('subProperty', `"${subProperty}"`)
1610
1658
 
1611
- if (_.isString(reducedValue) && reducedValue.match(deepRefSyntax)) { // build mode
1659
+ if (isString(reducedValue) && reducedValue.match(deepRefSyntax)) {
1660
+ // build mode
1612
1661
  reducedValue = appendDeepVariable(reducedValue, subProperty)
1613
- } else { // get mode
1662
+ } else {
1663
+ // get mode
1614
1664
  if (typeof reducedValue === 'undefined') {
1615
1665
  // was reducedValue = {}
1616
1666
  // Adding internal flag signals this value is unknown
@@ -1629,7 +1679,7 @@ Please use ":" to reference sub properties`
1629
1679
  } catch (e) {}
1630
1680
 
1631
1681
  reducedValue = reducedValue[subProperty]
1632
- } else if (_.isString(reducedValue)) {
1682
+ } else if (isString(reducedValue)) {
1633
1683
  try {
1634
1684
  // if JSON parse it
1635
1685
  reducedValue = JSON.parse(reducedValue)
@@ -1666,126 +1716,14 @@ Please use ":" to reference sub properties`
1666
1716
  // console.log("MISSING", variableString)
1667
1717
  // console.log(this.deep)
1668
1718
  // console.log(valueToPopulate)
1669
-
1670
1719
  const notFoundMsg = `No ${varType} found to satisfy the '\${${variableString}}' variable. Attempting fallback value`
1671
1720
  if (DEBUG) {
1672
1721
  console.log(notFoundMsg)
1673
1722
  }
1674
-
1675
1723
  // errors make fallbacks not function. throw new Error(errorMsg)
1676
1724
  }
1677
1725
  return valueToPopulate
1678
1726
  }
1679
1727
  }
1680
1728
 
1681
- function findNestedVariable(split, originalSource) {
1682
- return split.find((thing) => {
1683
- if (originalSource && typeof originalSource === 'string') {
1684
- return originalSource.indexOf(`\${${thing}}`) > -1
1685
- }
1686
- return false
1687
- })
1688
- }
1689
-
1690
- /**
1691
- * Get fallback variable string
1692
- * @param {array} split - array from split at comma
1693
- * @param {string} nestedVar - fallback variable to reconstruct variable string from
1694
- * @return {string} - returns new ${variable, string}
1695
- */
1696
- function getFallbackString(split, nestedVar) {
1697
- let isSet = false
1698
- const newVar = split.reduce((acc, curr) => {
1699
- if (curr === nestedVar || isSet) {
1700
- acc = acc.concat(curr)
1701
- isSet = true
1702
- }
1703
- return acc
1704
- }, []).join(', ')
1705
- const cleanC = `\${${newVar.replace(/^\${/, '').replace(/}$/, '')}}`
1706
- return cleanC
1707
- }
1708
-
1709
- function verifyVariable(variableString, valueObject, variableTypes, config) {
1710
- const isRealVariable = variableTypes.some((r, i) => {
1711
- if (r.match instanceof RegExp && variableString.match(r.match)) {
1712
- return true
1713
- } else if (typeof r.match === 'function') {
1714
- if (r.match(variableString, config, valueObject)) {
1715
- return true
1716
- }
1717
- }
1718
- return false
1719
- })
1720
- // If not found in variable resolvers and is missing a colon throw
1721
- if (!isRealVariable && variableString.match(/:/)) {
1722
- // console.log('variableString', variableString)
1723
- throw new Error(`
1724
- Variable \${${variableString}} is invalid variable syntax.
1725
- Value Path: ${(valueObject.path) ? valueObject.path.join('.') : 'na'}
1726
- Original Value: ${valueObject.originalSource}
1727
-
1728
- Remove or update the \${${variableString}} to fix
1729
- `)
1730
- }
1731
- return isRealVariable
1732
- }
1733
-
1734
- function encodeUnknown(v) {
1735
- return `>passthrough[_[${Buffer.from(v).toString('base64')}]_]`
1736
- }
1737
-
1738
- function decodeUnknown(rawValue) {
1739
- const x = findUnknownValues(rawValue)
1740
- let val = rawValue.replace(/>passthrough/g, '')
1741
- if (x.length) {
1742
- x.forEach(({ match, value }) => {
1743
- // console.log('match', match)
1744
- const decodedValue = Buffer.from(value, 'base64').toString('ascii')
1745
- // console.log('decodedValue', decodedValue)
1746
- val = val.replace(match, decodedValue)
1747
- })
1748
- }
1749
- return val
1750
- }
1751
-
1752
- function findUnknownValues(text) {
1753
- let matches
1754
- let links = []
1755
- while ((matches = base64WrapperRegex.exec(text)) !== null) {
1756
- if (matches.index === base64WrapperRegex.lastIndex) {
1757
- base64WrapperRegex.lastIndex++ // avoid infinite loops with zero-width matches
1758
- }
1759
- links.push({
1760
- match: matches[0],
1761
- value: matches[1]
1762
- })
1763
- }
1764
- return links
1765
- }
1766
-
1767
- function isPromise(obj) { // eslint-disable-line
1768
- return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
1769
- }
1770
-
1771
- // TODO fix argument parsing to handle commas
1772
- function splitCsv(str, splitter) {
1773
- const splitSyntax = splitter || ','
1774
- // Split at comma SPACE ", "
1775
- return str.split(splitSyntax).reduce((accum, curr) => {
1776
- if (accum.isConcatting) {
1777
- accum.soFar[accum.soFar.length - 1] += ',' + curr
1778
- } else {
1779
- accum.soFar.push(curr)
1780
- }
1781
- if (curr.split('"').length % 2 == 0) { // eslint-disable-line
1782
- accum.isConcatting = !accum.isConcatting
1783
- }
1784
- return accum
1785
- }, {
1786
- soFar: [],
1787
- isConcatting: false
1788
- }).soFar
1789
- }
1790
-
1791
1729
  module.exports = Configorama