configorama 0.4.10 → 0.5.1

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.
Files changed (40) hide show
  1. package/cli.js +116 -0
  2. package/package.json +9 -6
  3. package/{lib → src}/main.js +371 -451
  4. package/{lib → src}/parsers/yaml.js +30 -18
  5. package/src/parsers/yaml.test.js +169 -0
  6. package/{lib → src}/resolvers/valueFromEnv.js +10 -0
  7. package/{lib → src}/resolvers/valueFromGit.js +95 -1
  8. package/{lib → src}/utils/PromiseTracker.js +7 -4
  9. package/src/utils/arrayToJsonPath.js +11 -0
  10. package/src/utils/find-project-root.js +25 -0
  11. package/{lib → src}/utils/formatFunctionArgs.js +1 -1
  12. package/src/utils/lodash.js +91 -0
  13. package/src/utils/mergeByKeys.js +29 -0
  14. package/src/utils/parse.js +62 -0
  15. package/src/utils/replaceAll.js +16 -0
  16. package/{lib → src}/utils/splitByComma.js +7 -2
  17. package/src/utils/splitCsv.js +29 -0
  18. package/src/utils/textUtils.js +31 -0
  19. package/src/utils/unknownValues.js +46 -0
  20. package/src/utils/variableUtils.js +52 -0
  21. /package/{lib → src}/functions/md5.js +0 -0
  22. /package/{lib → src}/index.js +0 -0
  23. /package/{lib → src}/parsers/hcl.js +0 -0
  24. /package/{lib → src}/parsers/index.js +0 -0
  25. /package/{lib → src}/parsers/json5.js +0 -0
  26. /package/{lib → src}/parsers/toml.js +0 -0
  27. /package/{lib → src}/resolvers/valueFromNumber.js +0 -0
  28. /package/{lib → src}/resolvers/valueFromOptions.js +0 -0
  29. /package/{lib → src}/resolvers/valueFromSelf.js +0 -0
  30. /package/{lib → src}/resolvers/valueFromString.js +0 -0
  31. /package/{lib → src}/sync.js +0 -0
  32. /package/{lib → src}/utils/appendDeepVariable.js +0 -0
  33. /package/{lib → src}/utils/cleanVariable.js +0 -0
  34. /package/{lib → src}/utils/cloudformationSchema.js +0 -0
  35. /package/{lib → src}/utils/deep-log.js +0 -0
  36. /package/{lib → src}/utils/getFullFilePath.js +0 -0
  37. /package/{lib → src}/utils/handleSignalEvents.js +0 -0
  38. /package/{lib → src}/utils/isValidValue.js +0 -0
  39. /package/{lib → src}/utils/regex/index.js +0 -0
  40. /package/{lib → src}/utils/trimSurroundingQuotes.js +0 -0
@@ -3,26 +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 isArray = require('lodash.isarray')
7
- const isString = require('lodash.isstring')
8
- const isNumber = require('lodash.isnumber')
9
- const isObject = require('lodash.isobject')
10
- const isDate = require('lodash.isdate')
11
- const isRegExp = require('lodash.isregexp')
12
- const isFunction = require('lodash.isfunction')
13
- const isEmpty = require('lodash.isempty')
14
- const trim = require('lodash.trim')
15
- const camelCase = require('lodash.camelcase')
16
- const kebabCase = require('lodash.kebabcase')
17
- const capitalize = require('lodash.capitalize')
18
- const split = require('lodash.split')
19
- const map = require('lodash.map')
20
- const mapValues = require('lodash.mapvalues')
21
- const assign = require('lodash.assign')
22
- const set = require('lodash.set')
23
- const cloneDeep = require('lodash.clonedeep')
6
+
24
7
  const findUp = require('find-up')
25
- const replaceAll = require('replaceall')
26
8
  const traverse = require('traverse')
27
9
  const dotProp = require('dot-prop')
28
10
  /* Default Value resolvers */
@@ -38,16 +20,29 @@ const TOML = require('./parsers/toml')
38
20
  const md5Function = require('./functions/md5')
39
21
 
40
22
  /* Utility/helpers */
41
- const splitByComma = require('./utils/splitByComma')
42
23
  const cleanVariable = require('./utils/cleanVariable')
43
24
  const appendDeepVariable = require('./utils/appendDeepVariable')
44
25
  const isValidValue = require('./utils/isValidValue')
45
26
  const PromiseTracker = require('./utils/PromiseTracker')
46
27
  const handleSignalEvents = require('./utils/handleSignalEvents')
47
28
  const formatFunctionArgs = require('./utils/formatFunctionArgs')
48
- const cloudFormationSchema = require('./utils/cloudformationSchema')
49
29
  const trimSurroundingQuotes = require('./utils/trimSurroundingQuotes')
50
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
+
51
46
  /**
52
47
  * Maintainer's notes:
53
48
  *
@@ -62,8 +57,10 @@ const deepLog = require('./utils/deep-log')
62
57
  * pause population, noting the continued depth to traverse. This motivated "deep" variables.
63
58
  * Original issue #4687
64
59
  */
65
-
66
60
  const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
61
+ const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
62
+ const deepIndexPattern = /deep\:(\d*)/
63
+ const deepPrefixReplacePattern = /(?:^deep:)\d+\.?/g
67
64
  const fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-\/,'" ]+?)\)/g)
68
65
  // TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
69
66
  // To match file(asyncValue.js, lol) input params
@@ -75,7 +72,9 @@ const funcStartOfLineRegex = /^(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
75
72
  const subFunctionRegex = /(\w+):(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
76
73
  const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
77
74
  const logLines = '─────────────────────────────────────────────────'
78
- const DEBUG = false
75
+
76
+ let DEBUG = process.argv.includes('--debug') ? true : false
77
+ // DEBUG = true
79
78
 
80
79
  const ENABLE_FUNCTIONS = true
81
80
 
@@ -94,17 +93,18 @@ class Configorama {
94
93
  allowUndefinedValues: false,
95
94
  }, options)
96
95
 
97
-
96
+ this.filterCache = {}
97
+
98
98
  const defaultSyntax = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
99
+
99
100
  const variableSyntax = options.syntax || defaultSyntax
100
101
  let varRegex
101
102
  if (typeof variableSyntax === 'string') {
102
- varRegex = RegExp(variableSyntax, 'g')
103
+ varRegex = new RegExp(variableSyntax, 'g')
103
104
  // this.variableSyntax = /\${((?!AWS)([ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?|(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*))}/
104
105
  } else if (variableSyntax instanceof RegExp) {
105
106
  varRegex = variableSyntax
106
107
  }
107
- // console.log('varRegex', varRegex)
108
108
  this.variableSyntax = varRegex
109
109
 
110
110
  // Set initial config object to populate
@@ -121,47 +121,15 @@ class Configorama {
121
121
  const fileDirectory = path.dirname(path.resolve(fileOrObject))
122
122
  const fileType = path.extname(fileOrObject)
123
123
 
124
- let configObject
125
- if (fileType.match(/\.(yml|yaml)/)) {
126
- try {
127
- const ymlText = YAML.preProcess(fileContents, varRegex)
128
- // console.log('ymlText', ymlText)
129
- configObject = YAML.parse(ymlText)
130
- } catch (err) {
131
- // Attempt to fix cloudformation refs
132
- if (err.message.match(/YAMLException/)) {
133
- const ymlText = YAML.preProcess(fileContents, varRegex)
134
- const result = YAML.load(ymlText, {
135
- filename: fileOrObject,
136
- schema: cloudFormationSchema.schema
137
- })
138
- if (result.error) {
139
- throw result.error
140
- }
141
- configObject = result.data
142
- }
143
- }
144
- } else if (fileType.match(/\.(toml)/)) {
145
- configObject = TOML.parse(fileContents)
146
- } else if (fileType.match(/\.(json)/)) {
147
- configObject = JSON.parse(fileContents)
148
- } else if (fileType.match(/\.(js)/)) {
149
- let jsFile
150
- try {
151
- jsFile = require(fileOrObject)
152
- if (typeof jsFile !== 'function') {
153
- configObject = jsFile
154
- } else {
155
- let jsArgs = opts.dynamicArgs || {}
156
- if (jsArgs && typeof jsArgs === 'function') {
157
- jsArgs = jsArgs()
158
- }
159
- configObject = jsFile(jsArgs)
160
- }
161
- } catch (err) {
162
- throw new Error(err)
163
- }
164
- }
124
+ // Parse file contents using extracted function
125
+ const configObject = parseFileContents(
126
+ fileContents,
127
+ fileType,
128
+ fileOrObject,
129
+ varRegex,
130
+ this.opts
131
+ )
132
+
165
133
  // set config objects
166
134
  this.config = configObject
167
135
  // Keep a copy
@@ -200,7 +168,7 @@ class Configorama {
200
168
  match: selfRefSyntax,
201
169
  resolver: (varString, o, x, pathValue) => {
202
170
  return this.getValueFromSelf(varString, o, x, pathValue)
203
- }
171
+ },
204
172
  },
205
173
  /**
206
174
  * File references
@@ -213,7 +181,7 @@ class Configorama {
213
181
  resolver: (varString, o, x, pathValue) => {
214
182
  // console.log('pathValue getValueFromFile', pathValue)
215
183
  return this.getValueFromFile(varString)
216
- }
184
+ },
217
185
  },
218
186
 
219
187
  // Git refs
@@ -225,20 +193,14 @@ class Configorama {
225
193
  // return this.getValueFromFunction(varString)
226
194
  // }
227
195
  // },
228
- // getValueFromString,
229
- {
230
- match: RegExp(/(?:('|").*?\1)/g),
231
- resolver: (varString, o, x, pathValue) => {
232
- // console.log('pathValue getValueFromString', pathValue)
233
- return this.getValueFromString(varString)
234
- }
235
- },
236
- // Deep references
196
+ /* Resolve string references */
197
+ getValueFromString,
198
+ /* Resolve deep references */
237
199
  {
238
200
  match: deepRefSyntax,
239
201
  resolver: (varString) => {
240
202
  return this.getValueFromDeep(varString)
241
- }
203
+ },
242
204
  },
243
205
  // Numbers
244
206
  getValueFromNumber,
@@ -269,7 +231,7 @@ class Configorama {
269
231
  },
270
232
  resolver: (varString, o, x, pathValue) => {
271
233
  return this.getValueFromSelf(varString, o, x, pathValue)
272
- }
234
+ },
273
235
  }
274
236
 
275
237
  /* Apply user defined variable sources */
@@ -282,6 +244,9 @@ class Configorama {
282
244
 
283
245
  // Additional filters on values. ${thing | filterFunction}
284
246
  this.filters = {
247
+ capitalize: (val) => {
248
+ return capitalize(val)
249
+ },
285
250
  toUpperCase: (val) => {
286
251
  if (typeof val === 'string') {
287
252
  return val.toUpperCase()
@@ -306,9 +271,23 @@ class Configorama {
306
271
  toKebabCase: (val) => {
307
272
  return kebabCase(val)
308
273
  },
309
- capitalize: (val) => {
310
- return capitalize(val)
311
- }
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
+ },
312
291
  }
313
292
 
314
293
  // Apply user defined filters
@@ -316,6 +295,10 @@ class Configorama {
316
295
  this.filters = Object.assign({}, this.filters, options.filters)
317
296
  }
318
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
+
319
302
  this.functions = {
320
303
  split: (value, delimiter, limit) => {
321
304
  const delimit = delimiter || ','
@@ -359,9 +342,9 @@ class Configorama {
359
342
  },
360
343
  math: () => {},
361
344
  upperKeys: (o) => {
362
- 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
363
346
  },
364
- md5: md5Function
347
+ md5: md5Function,
365
348
  }
366
349
 
367
350
  // Apply user defined functions
@@ -393,66 +376,72 @@ class Configorama {
393
376
  const configoramaOpts = this.opts
394
377
  const originalConfig = this.originalConfig
395
378
  return this.initialCall(() => {
396
- return Promise.resolve().then(() => {
397
- return this.populateObjectImpl(this.config).finally(() => {
398
- // TODO populate function values here?
399
- // console.log('Final Config', this.config)
400
- const transform = this.runFunction.bind(this)
401
- const varSyntax = this.variableSyntax
402
- // Traverse resolved object and run functions
403
- // console.log('this.config', this.config)
404
- traverse(this.config).forEach(function (rawValue) {
405
- /* Pass through unknown variables */
406
- if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
407
- const configValuePath = this.path.join('.')
408
- const ogValue = dotProp.get(originalConfig, configValuePath)
409
- const varDisplay = (ogValue) ? `"${ogValue}" variable` : 'variable'
410
- 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 = `
411
395
  Config error:
412
396
  "${configValuePath}" resolved to "undefined"
413
397
  Verify the ${varDisplay} in config at "${configValuePath}"`
414
- throw new Error(errorMessage)
415
- }
416
- if (typeof rawValue === 'string') {
417
- /* Process inline functions like merge() */
418
- if (ENABLE_FUNCTIONS && rawValue.match(/> function /)) {
419
- // console.log('RAW FUNCTION', rawFunction)
420
- const funcString = rawValue.replace(/> function /g, '')
421
- // console.log('funcString', funcString)
422
- const func = cleanVariable(funcString, varSyntax, true)
423
- const funcVal = transform(func)
424
- const hasObjectRef = rawValue.match(/\.\S*$/)
425
- if (hasObjectRef && typeof funcVal === 'object') {
426
- const objectPath = hasObjectRef[0].replace(/^\./, '')
427
- // console.log('objectPath', objectPath)
428
- /* get value from object and update */
429
- const valueFromObject = dotProp.get(funcVal, objectPath)
430
- // console.log('valueFromObject', valueFromObject)
431
- this.update(valueFromObject)
432
- } else {
433
- this.update(funcVal)
434
- }
398
+ throw new Error(errorMessage)
435
399
  }
436
-
437
-
438
- /* Allow for unknown variables to pass through */
439
- if (rawValue.match(/>passthrough/)) {
440
- const newValues = decodeUnknown(rawValue)
441
- // console.log('>>>> newValues', newValues)
442
- 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
+ }
443
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)
444
434
  }
445
435
  })
446
-
447
- if (DEBUG) {
448
- console.log(`Variable process ran ${this.callCount} times`)
449
- // console.log('FINAL Value', this.config)
450
- // 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)
451
442
  }
443
+ return this.config
452
444
  })
453
- }).then(() => {
454
- return this.config
455
- })
456
445
  })
457
446
  }
458
447
  runFunction(variableString) {
@@ -470,7 +459,6 @@ class Configorama {
470
459
  // test for object
471
460
  const functionName = hasFunc[1]
472
461
  const rawArgs = hasFunc[2]
473
-
474
462
  // TODO @DWELLS. Loop through all raw args and parse to correct datatype
475
463
  // argument is object
476
464
  let argsToPass
@@ -488,9 +476,7 @@ class Configorama {
488
476
  // TODO check for camelCase version. | toUpperCase messes with function name
489
477
  const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
490
478
 
491
- if (!theFunction) {
492
- throw new Error(`Function "${functionName}" not found`)
493
- }
479
+ if (!theFunction) throw new Error(`Function "${functionName}" not found`)
494
480
 
495
481
  const funcValue = theFunction(...argsToPass)
496
482
  // console.log('funcValue', funcValue)
@@ -545,13 +531,10 @@ class Configorama {
545
531
  */
546
532
  getProperties(root, atRoot, current, _context, _results) {
547
533
  let context = _context
548
- if (!context) {
549
- context = []
550
- }
534
+ if (!context) context = []
551
535
  let results = _results
552
- if (!results) {
553
- results = []
554
- }
536
+ if (!results) results = []
537
+
555
538
  const addContext = (value, key) => {
556
539
  return this.getProperties(root, false, value, context.concat(key), results)
557
540
  }
@@ -567,13 +550,12 @@ class Configorama {
567
550
  path: context,
568
551
  value: current,
569
552
  }
570
-
571
- 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]
572
554
  let originalValue = dotProp.get(this.originalConfig, thePath)
573
555
  // TODO @DWELLS make recursive
574
556
  if (!originalValue) {
575
557
  const parentArray = leaf.path.slice(0, -1)
576
- const parentPath = (parentArray > 1) ? parentArray.join('.') : parentArray[0]
558
+ const parentPath = parentArray > 1 ? parentArray.join('.') : parentArray[0]
577
559
  originalValue = dotProp.get(this.originalConfig, parentPath)
578
560
  }
579
561
  leaf.originalSource = originalValue
@@ -583,7 +565,7 @@ class Configorama {
583
565
  leaf.isFileRef = true
584
566
  }
585
567
  }
586
- // dotProp.get(this.originalConfig, thePath)
568
+ // dotProp.get(this.originalConfig, thePath)
587
569
  results.push(leaf)
588
570
  }
589
571
  return results
@@ -605,12 +587,12 @@ class Configorama {
605
587
  // Initial check if value has variable string in it
606
588
  return isString(property.value) && property.value.match(this.variableSyntax)
607
589
  })
590
+ // console.log('variables', variables)
608
591
  return map(variables, (valueObject) => {
609
592
  // console.log('valueObject', valueObject)
610
- return this.populateValue(valueObject, false)
611
- .then((populated) => {
612
- return assign({}, valueObject, { populated: populated.value })
613
- })
593
+ return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
594
+ return assign({}, valueObject, { populated: populated.value })
595
+ })
614
596
  })
615
597
  }
616
598
  /**
@@ -620,13 +602,15 @@ class Configorama {
620
602
  * @returns {Promise<number>} resolving with the number of changes that were applied to the given
621
603
  * target
622
604
  */
623
- assignProperties(target, populations) { // eslint-disable-line class-methods-use-this
624
- return Promise.all(populations)
625
- .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) => {
626
609
  if (result.value !== result.populated) {
627
610
  set(target, result.path, result.populated)
628
611
  }
629
- }))
612
+ })
613
+ })
630
614
  }
631
615
  /**
632
616
  * Populate the variables in the given object.
@@ -638,20 +622,19 @@ class Configorama {
638
622
  }
639
623
  populateObjectImpl(objectToPopulate) {
640
624
  this.callCount = this.callCount + 1
641
-
625
+
642
626
  if (DEBUG) {
643
- deepLog(`objectToPopulate ${this.callCount}`, objectToPopulate)
627
+ deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
628
+ // process.exit(0)
644
629
  }
645
630
 
646
631
  const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
632
+ // console.log('leaves', leaves)
647
633
  const populations = this.populateVariables(leaves)
648
-
649
634
  // console.log("FILL LEAVES", populations)
650
635
 
651
636
  if (populations.length === 0) {
652
- if (DEBUG) {
653
- console.log('Config Population Finished')
654
- }
637
+ if (DEBUG) console.log('Config Population Finished')
655
638
  return Promise.resolve(objectToPopulate)
656
639
  }
657
640
 
@@ -674,20 +657,15 @@ class Configorama {
674
657
  * @returns {Object|String|MatchResult[]} The given property or the identified matches
675
658
  */
676
659
  getMatches(property) {
677
- if (typeof property !== 'string') {
678
- return property
679
- }
660
+ if (typeof property !== 'string') return property
680
661
  const matches = property.match(this.variableSyntax)
681
-
682
- if (!matches || !matches.length) {
683
- return property
684
- }
685
- return map(matches, match => {
662
+ if (!matches || !matches.length) return property
663
+ return map(matches, (match) => {
686
664
  // console.log('match', match)
687
- return ({
665
+ return {
688
666
  match: match,
689
667
  variable: cleanVariable(match, this.variableSyntax),
690
- })
668
+ }
691
669
  })
692
670
  }
693
671
  /**
@@ -723,7 +701,6 @@ class Configorama {
723
701
  if (results[i] && typeof results[i] === 'object' && results[i].__internal_only_flag) {
724
702
  valueToPop = results[i].value
725
703
  }
726
-
727
704
  result = this.populateVariable(valueObject, matches[i].match, valueToPop)
728
705
  /*
729
706
  console.log('> valueToPop', valueToPop)
@@ -742,30 +719,27 @@ class Configorama {
742
719
  * @returns {PromiseLike<T>} A promise that resolves to the populated value, recursively if root
743
720
  * is true
744
721
  */
745
- populateValue(valueObject, root) {
722
+ populateValue(valueObject, root, caller) {
746
723
  if (DEBUG) {
747
724
  console.log('─────────────────────────────────────────────▶')
748
- console.log('>>>>>>>> populateValue')
725
+ console.log('>>>>>>>> populateValue', caller)
749
726
  console.log(valueObject)
750
727
  }
751
728
  const property = valueObject.value
752
729
  const matches = this.getMatches(property)
753
730
  /*
754
- console.log('matchesmatches', matches)
731
+ console.log('populateValue matches', matches)
755
732
  /** */
756
733
  if (!isArray(matches)) {
757
734
  return Promise.resolve(property)
758
735
  }
759
736
  const populations = this.populateMatches(matches, valueObject, root)
760
737
  return Promise.all(populations)
761
- .then(results => this.renderMatches(valueObject, matches, results))
738
+ .then((results) => this.renderMatches(valueObject, matches, results))
762
739
  .then((result) => {
763
- /*
764
- console.log('renderMatches result', result)
765
- /** */
766
-
740
+ // console.log('renderMatches result', result)
767
741
  if (root && isArray(matches)) {
768
- return this.populateValue({ value: result.value }, root)
742
+ return this.populateValue({ value: result.value }, root, 'self populateValue')
769
743
  }
770
744
  return result
771
745
  })
@@ -794,29 +768,26 @@ class Configorama {
794
768
  console.log('valueObject', valueObject)
795
769
  console.log('root', root)
796
770
  }
797
-
798
771
  /* requires node 8.11+
799
772
  if (valueObject.value.match(/(?<!^)> function /)) {
800
773
  // valueObject.value = valueObject.value.replace(/(?<!^)> function /, '')
801
774
  // valueObject.value = valueObject.value.replace(/^> function /, '')
802
775
  // valueObject.value = `> function ${valueObject.value}`
803
- }
804
- */
776
+ }*/
805
777
 
806
778
  const parts = splitByComma(variable)
807
-
808
- if (parts.length > 1) {
809
- if (DEBUG) {
810
- console.log('parts', parts)
811
- console.log('parts variable:', variable)
812
- console.log('parts property:', valueObject.value)
813
- console.log('All parts:', parts)
814
- console.log('-----')
815
- }
816
- 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('-----')
817
785
  }
818
-
819
- 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)
820
791
  }
821
792
  /**
822
793
  * Populate a given property, given the matched string to replace and the value to replace the
@@ -829,35 +800,54 @@ class Configorama {
829
800
  */
830
801
  populateVariable(valueObject, matchedString, valueToPopulate) {
831
802
  let property = valueObject.value
803
+ // console.log('init property', property)
804
+ let DEBUG_TYPE = false
832
805
  if (DEBUG) {
833
- console.log('────────START populateVariable──────────────')
834
- console.log('populateVariable: valueToPopulate', valueToPopulate)
835
- console.log('populateVariable: valueObject', valueObject)
836
- console.log('populateVariable: property', property)
837
- 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)
838
824
  }
825
+ // console.log('foundFilters', foundFilters)
839
826
 
840
827
  // total replacement
841
828
  if (property === matchedString) {
829
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE total replacement')
842
830
  const v = valueObject.value || ''
843
- const originalSrc = valueObject.originalSource || ''
844
831
  property = valueToPopulate
832
+
845
833
  /* Handle ${self:custom.ref, ''} with deep values */
846
834
  if (v.match(deepRefSyntax) && originalSrc.match(this.variableSyntax) && !v.match(/deep\:(\d*)\..*}$/)) {
847
835
  // console.log('deep var', this.deep)
848
836
  // console.log('originalSrc', originalSrc)
849
837
  // console.log('value', v)
850
- let deepIndex = Number(v.match(/deep\:(\d*)/)[1])
838
+ let deepIndex = Number(v.match(deepIndexPattern)[1])
851
839
  let item = this.deep[deepIndex]
852
-
840
+
853
841
  if (item.match(deepRefSyntax)) {
854
- deepIndex = Number(item.match(/deep\:(\d*)/)[1])
842
+ deepIndex = Number(item.match(deepIndexPattern)[1])
855
843
  item = this.deep[deepIndex]
856
844
  }
857
845
  property = this.deep[deepIndex]
846
+ // console.log('NEW PROPERTY after deep ref', property)
858
847
  }
859
848
  // partial replacement, string
860
849
  } else if (isString(valueToPopulate)) {
850
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE isString')
861
851
  // if (property.match(/^> function /g)) {
862
852
  //
863
853
  // const innerFunc = /> function (\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
@@ -867,7 +857,6 @@ class Configorama {
867
857
  // console.log('xxxx', rep)
868
858
  // console.log('valueToPopulate', valueToPopulate)
869
859
  // }
870
-
871
860
 
872
861
  let currentMatchedString = matchedString
873
862
  /* Address fall through values if found */
@@ -877,7 +866,6 @@ class Configorama {
877
866
  currentMatchedString = valueObject.value
878
867
  }
879
868
  }
880
-
881
869
  /*
882
870
  console.log('>------')
883
871
  console.log('isString og matchedString', matchedString)
@@ -888,28 +876,56 @@ class Configorama {
888
876
  console.log('isString currentMatchedString', currentMatchedString)
889
877
  console.log('>------')
890
878
  /** */
891
-
892
- // (replaceThis, withThis, inThis)
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
+
893
888
  property = replaceAll(currentMatchedString, valueToPopulate, property)
894
- // console.log('property', property)
889
+ // console.log('property replaceAll', property)
895
890
 
896
891
  // if (property.match(/^> function /g)) {
897
892
  // console.log('REPLACE after', property)
898
893
  // }
899
-
894
+
900
895
  // partial replacement, number
901
896
  } else if (isNumber(valueToPopulate)) {
897
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE isNumber')
902
898
  property = replaceAll(matchedString, String(valueToPopulate), property)
903
- // TODO This was temp fix for array value mismatch from filters. This fixes filterInner: ${commas | split(${self:inner}, 2) }
904
- // } else if (isArray(valueToPopulate) && valueToPopulate.length === 1) {
905
- // property = replaceAll(matchedString, String(valueToPopulate[0]), 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)
906
902
  } else if (isObject(valueToPopulate)) {
907
- // console.log('OBJECT MATCH', valueToPopulate)
908
- property = replaceAll(matchedString, JSON.stringify(valueToPopulate), property)// .replace(/}$/, '').replace(/^\$\{/, '')
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(/^\$\{/, '')
909
924
  // console.log('property', property)
910
925
  // TODO run functions here
911
926
  // console.log('other new prop', property)
912
927
  } else {
928
+ if (DEBUG_TYPE) console.log('DEBUG_TYPE else')
913
929
  let missingValue = matchedString
914
930
 
915
931
  if (matchedString.match(deepRefSyntax)) {
@@ -935,18 +951,15 @@ class Configorama {
935
951
  originalSource: valueObject.originalSource,
936
952
  // set __internal_only_flag to note this is object we make not a resolved value
937
953
  __internal_only_flag: true,
938
- caller: 'nestedVar'
954
+ caller: 'nestedVar',
939
955
  }
940
956
  }
941
957
 
942
958
  const errorMessage = `
943
959
  Missing Value ${missingValue} - ${matchedString}
944
-
945
- Make sure the property is being passed in correctly
946
-
947
- For variable:
948
-
949
- ${valueObject.path}: ${valueObject.originalSource}
960
+ \nMake sure the property is being passed in correctly
961
+ \nFor variable:
962
+ \n${valueObject.path}: ${valueObject.originalSource}
950
963
  `
951
964
  throw new Error(errorMessage)
952
965
  }
@@ -964,10 +977,10 @@ ${valueObject.path}: ${valueObject.originalSource}
964
977
  if (func && property.match(/^> function /g)) {
965
978
  /* IMPORTANT fix `finalProp` for nested function reference
966
979
  nestedOne: 'hi'
967
- nestedTwo: ${merge('haha', 'wowowow')}
980
+ nestedTwo: ${merge('nice', 'wow')}
968
981
  mergeNested: ${merge('lol', ${nestedTwo})}
969
982
  */
970
- const finalProp = (property.match(/(?<!^)> function /)) ? prop : property
983
+ const finalProp = property.match(/(?<!^)> function /) ? prop : property
971
984
 
972
985
  return {
973
986
  value: finalProp, // prop to fix nested ¯\_(ツ)_/¯
@@ -1007,17 +1020,44 @@ ${valueObject.path}: ${valueObject.originalSource}
1007
1020
  // property = newer
1008
1021
  // }
1009
1022
  }
1010
- // 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)
1011
1051
  // console.log('XXXX path', valueObject.path)
1012
1052
  // console.log('XXXX originalSource', valueObject.originalSource)
1053
+ // console.log('end property', property)
1013
1054
  return {
1014
1055
  value: property,
1015
1056
  path: valueObject.path,
1016
1057
  originalSource: valueObject.originalSource,
1017
- // set __internal_only_flag to note this is object we make not a resolved value
1018
- __internal_only_flag: true,
1058
+ __internal_only_flag: true, // set __internal_only_flag to note this is object we make not a resolved value
1019
1059
  caller: 'end',
1020
- count: this.callCount
1060
+ count: this.callCount,
1021
1061
  }
1022
1062
  }
1023
1063
  // ###############
@@ -1033,16 +1073,13 @@ ${valueObject.path}: ${valueObject.originalSource}
1033
1073
  overwrite(variableStrings, valueObject) {
1034
1074
  const propertyString = valueObject.value
1035
1075
  // console.log('variableStrings', variableStrings)
1036
-
1037
1076
  // console.log('propertyString', typeof propertyString)
1038
1077
  const variableValues = variableStrings.map((variableString) => {
1039
1078
  // This runs on nested variable resolution
1040
1079
  return this.getValueFromSource(variableString, valueObject, 'overwrite')
1041
1080
  })
1042
-
1043
1081
  // console.log('variableValues', variableValues)
1044
-
1045
- return Promise.all(variableValues).then(values => {
1082
+ return Promise.all(variableValues).then((values) => {
1046
1083
  let deepPropertyStr = propertyString
1047
1084
  let deepProperties = 0
1048
1085
  values.forEach((value, index) => {
@@ -1059,19 +1096,20 @@ ${valueObject.path}: ${valueObject.originalSource}
1059
1096
  })
1060
1097
  return deepProperties > 0
1061
1098
  ? Promise.resolve(deepPropertyStr) // return deep variable replacement of original
1062
- : Promise.resolve(values.find(isValidValue))// resolve first valid value, else undefined
1099
+ : Promise.resolve(values.find(isValidValue)) // resolve first valid value, else undefined
1063
1100
  })
1064
1101
  }
1065
-
1066
1102
  /**
1067
1103
  * Given any variable string, return the value it should be populated with.
1068
1104
  * @param variableString The variable string to retrieve a value for.
1069
1105
  * @returns {Promise.<TResult>|*} A promise resolving to the given variables value.
1070
1106
  */
1071
1107
  getValueFromSource(variableString, valueObject, caller) {
1108
+ // console.log('getValueFromSource caller', caller)
1072
1109
  const propertyString = valueObject.value
1073
1110
  const pathValue = valueObject.path
1074
- // return resolved value
1111
+ // console.log('getValueFromSource propertyString', propertyString)
1112
+ // console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
1075
1113
  if (this.tracker.contains(variableString)) {
1076
1114
  // console.log('try to get', variableString)
1077
1115
  return this.tracker.get(variableString, propertyString)
@@ -1089,28 +1127,38 @@ ${valueObject.path}: ${valueObject.originalSource}
1089
1127
  }
1090
1128
 
1091
1129
  const filters = propertyString.match(/\s\|/)
1130
+ let promiseKey
1092
1131
  // TODO match () or pipes |
1093
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)
1094
1138
  // console.log('variableString', variableString)
1139
+ promiseKey = deeperValue.match(/\s\|/) ? deeperValue : undefined
1095
1140
 
1096
1141
  // TODO clean this up
1097
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)
1098
1155
 
1099
- const string = cleanVariable(propertyString, this.variableSyntax, true)
1100
- const filterz = string.split('|').filter((value, index, arr) => {
1101
- return index > 0
1102
- }).map((f) => {
1103
- return trim(f)
1104
- })
1105
- // console.log('filters to run', filterz)
1106
-
1107
- newHasFilter = filterz
1156
+ newHasFilter = _filter
1108
1157
  // If current variable string has no pipes, it has no filters
1109
1158
  if (!variableString.match(/\|/)) {
1110
1159
  newHasFilter = null
1111
1160
  }
1112
- // console.log('HAS FILTERS', filters)
1113
- // console.log('t', t)
1161
+ // console.log('newHasFilter', newHasFilter)
1114
1162
  variableString = trim(t[0])
1115
1163
  }
1116
1164
 
@@ -1131,19 +1179,15 @@ ${valueObject.path}: ${valueObject.originalSource}
1131
1179
  }
1132
1180
  return false
1133
1181
  })
1134
-
1135
1182
  // console.log('found', found)
1136
1183
 
1137
1184
  if (found && resolverFunction) {
1138
1185
  // TODO finalize resolverFunction API
1139
- const valuePromise = resolverFunction(
1140
- variableString,
1141
- this.options,
1142
- this.config,
1143
- valueObject
1144
- ).then((val) => {
1186
+ const valuePromise = resolverFunction(variableString, this.options, this.config, valueObject).then((val) => {
1145
1187
  // console.log('VALUE', val)
1146
- if (val === null || typeof val === 'undefined' ||
1188
+ if (
1189
+ val === null ||
1190
+ typeof val === 'undefined' ||
1147
1191
  /* match deep refs as empty {}, they need resolving via functions */
1148
1192
  (typeof val === 'object' && isEmpty(val) && variableString.match(/deep\:/))
1149
1193
  ) {
@@ -1153,9 +1197,8 @@ ${valueObject.path}: ${valueObject.originalSource}
1153
1197
 
1154
1198
  if (variableString.match(/deep\:/)) {
1155
1199
  // return Promise.resolve(this.getValueFromDeep(variableString))
1156
- const deepIndex = variableString.match(/deep\:(\d*)/)
1157
- const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g)
1158
- const deepRef = variableString.replace(deepPrefixReplace, '')
1200
+ const deepIndex = variableString.match(deepIndexPattern)
1201
+ const deepRef = variableString.replace(deepPrefixReplacePattern, '')
1159
1202
  // console.log('deepRef', deepRef)
1160
1203
  // console.log('deepIndexMatch', deepIndex)
1161
1204
  if (deepIndex[1] && this.deep.length) {
@@ -1170,22 +1213,21 @@ ${valueObject.path}: ${valueObject.originalSource}
1170
1213
  // TODO throw on empty values?
1171
1214
  // No fallback value found AND this is undefined, throw error
1172
1215
  if (valueCount.length === 1) {
1173
- throw new Error(`Error: Variable not found.
1174
- Variable: "${variableString}" from ${propertyString}
1175
- Value Path: ${(valueObject.path) ? valueObject.path.join('.') : 'na'}
1176
- Original Value: ${valueObject.originalSource}
1177
- Please fix this reference or provide a valid fallback value.
1178
- 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`)
1179
1220
  }
1180
-
1181
1221
  // no value resolved but fallback value exists, keep moving on
1182
1222
  return Promise.resolve(val)
1183
1223
  }
1224
+ /*
1184
1225
  // console.log('------')
1185
1226
  // console.log('propertyString', propertyString)
1186
1227
  // console.log('resolved val', val)
1187
1228
  // console.log('------')
1188
1229
  // console.log('newHasFilter', newHasFilter)
1230
+ /** */
1189
1231
  // No filters found. return value
1190
1232
  if (!newHasFilter) {
1191
1233
  return Promise.resolve(val)
@@ -1197,47 +1239,50 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1197
1239
  }
1198
1240
  return acc.concat({
1199
1241
  filter: this.filters[currentFilter],
1242
+ filterName: currentFilter,
1200
1243
  // args: argsToPass
1201
1244
  })
1202
1245
  }, [])
1246
+ // console.log('newUse', newUse)
1203
1247
 
1204
1248
  if (typeof val === 'string' && val.match(/deep:/)) {
1205
1249
  // TODO refactor the deep filter logic here. match | filter | filter..
1206
- const allFilters = propertyString.replace(/}$/, '').split('|').reduce((acc, currentFilter, i) => {
1207
- 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)}`
1208
1258
  return acc
1209
- }
1210
- acc += `| ${trim(currentFilter)}`
1211
- return acc
1212
- }, '')
1213
-
1259
+ }, '')
1214
1260
  // add filters to deep references if filter is used
1215
- const deepValueWithFilters = (newHasFilter[1]) ? val.replace(/}$/, ` ${allFilters}}`) : val
1261
+ const deepValueWithFilters = newHasFilter[1] ? val.replace(/}$/, ` ${allFilters}}`) : val
1216
1262
  // console.log('deepValueWithFilters', deepValueWithFilters)
1217
1263
  return Promise.resolve(deepValueWithFilters)
1218
1264
  }
1219
-
1220
- // console.log('newUse', newUse)
1221
1265
  /* Loop over filters used and produce new value */
1222
1266
  const newValue = newUse.reduce((a, c) => {
1223
1267
  // Fix for async value resolution. That code file refs returns object with .value
1224
- const theValue = (typeof a === 'object' && a.__internal_only_flag) ? a.value : a
1225
-
1226
- if (typeof c.filter === 'function') {
1227
- if (c.args) {
1228
- return c.filter(theValue, ...c.args)
1229
- }
1230
- 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
1231
1271
  }
1232
- return theValue
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')
1275
+ }
1276
+ this.filterCache[pathValue] = (this.filterCache[pathValue] || []).concat(c.filterName)
1277
+ return c.filter(theValue, 'from getValueFromSource')
1233
1278
  }, val)
1279
+ // console.log('newValue', newValue)
1234
1280
 
1235
1281
  return Promise.resolve(newValue)
1236
1282
  })
1237
-
1283
+ // console.log('newHasFilter', newHasFilter)
1238
1284
  // TODO do something with func here?
1239
-
1240
- return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter)
1285
+ return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter, promiseKey)
1241
1286
  }
1242
1287
 
1243
1288
  /* fall through case with self refs */
@@ -1271,7 +1316,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1271
1316
  }
1272
1317
  const fallbackStr = getFallbackString(split, nestedVar)
1273
1318
  return this.getValueFromSource(variableString, {
1274
- value: fallbackStr
1319
+ value: fallbackStr,
1275
1320
  }, 'nestedVar')
1276
1321
  }
1277
1322
 
@@ -1283,7 +1328,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1283
1328
  if (fallbackValue) {
1284
1329
  // recurse on fallback and check again
1285
1330
  return this.getValueFromSource(`${variableString})`, {
1286
- value: propertyString
1331
+ value: propertyString,
1287
1332
  }, 'cleanClean.match(fileRefSyntax)')
1288
1333
  }
1289
1334
  }
@@ -1299,20 +1344,23 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1299
1344
 
1300
1345
  // has fallback but needs deeper lookup. Call getValueFromSource again
1301
1346
  if (fallbackValue) {
1302
- if (DEBUG) {
1303
- console.log('fallbackValue', fallbackValue)
1304
- }
1347
+ if (DEBUG) console.log('fallbackValue', fallbackValue)
1348
+ // console.log('fallbackValue', fallbackValue)
1305
1349
  // recurse on fallback and check again
1306
- return this.getValueFromSource(fallbackValue, {
1307
- value: propertyString
1308
- }, 'fallbackValue')
1350
+ return this.getValueFromSource(
1351
+ fallbackValue,
1352
+ {
1353
+ value: propertyString,
1354
+ },
1355
+ 'fallbackValue',
1356
+ )
1309
1357
  }
1310
1358
  }
1311
1359
 
1312
1360
  // Variable NOT FOUND. Warn user
1313
1361
  const errorMessage = [
1314
1362
  `Invalid variable reference syntax`,
1315
- `Key: "${(valueObject.path) ? valueObject.path.join('.') : 'na'}"`,
1363
+ `Key: "${valueObject.path ? valueObject.path.join('.') : 'na'}"`,
1316
1364
  `Variable: "${variableString}" from ${propertyString} not found`,
1317
1365
  ]
1318
1366
 
@@ -1343,28 +1391,21 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1343
1391
 
1344
1392
  return this.tracker.add(variableString, notFoundPromise, propertyString, newHasFilter)
1345
1393
  }
1346
- getValueFromString(variableString) {
1347
- const valueToPopulate = variableString.replace(/^['"]|['"]$/g, '')
1348
- return Promise.resolve(valueToPopulate)
1349
- }
1350
-
1351
1394
  getValueFromSelf(variableString, o, x, data) {
1352
1395
  /*
1353
1396
  console.log('getValueFromSelf variableString', variableString)
1354
1397
  /** */
1355
-
1356
1398
  // console.log('self data', data)
1357
-
1358
1399
  const split = variableString.split(':')
1359
- const variable = (split.length && split[1]) ? split[1] : variableString
1400
+ const variable = split.length && split[1] ? split[1] : variableString
1360
1401
  const valueToPopulate = this.config
1361
- let deepProperties = variable.split('.').filter(property => property)
1402
+ let deepProperties = variable.split('.').filter((property) => property)
1362
1403
  // console.log('self deep', deepProperties)
1363
1404
  // console.log('self valueToPopulate', valueToPopulate)
1364
1405
 
1365
1406
  /* its file ref so we need to shift lookup for self in nested files */
1366
1407
  if (data.isFileRef) {
1367
- const dotPropPath = (deepProperties.length > 1) ? deepProperties.join('.') : deepProperties[0]
1408
+ const dotPropPath = deepProperties.length > 1 ? deepProperties.join('.') : deepProperties[0]
1368
1409
  const exists = dotProp.get(valueToPopulate, dotPropPath)
1369
1410
  // console.log('self exists', exists)
1370
1411
  if (!exists) {
@@ -1373,7 +1414,6 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1373
1414
  // console.log('self fixed deepProperties', deepProperties)
1374
1415
  }
1375
1416
  }
1376
-
1377
1417
  return this.getDeeperValue(deepProperties, valueToPopulate).then((res) => {
1378
1418
  /*
1379
1419
  console.log('self getDeeperValue variableString', variableString)
@@ -1382,7 +1422,6 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1382
1422
  return res
1383
1423
  })
1384
1424
  }
1385
-
1386
1425
  getValueFromFile(variableString) {
1387
1426
  // console.log('From file', `"${variableString}"`)
1388
1427
  let matchedFileString = variableString.match(fileRefSyntax)[0]
@@ -1410,12 +1449,11 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1410
1449
  }
1411
1450
  // console.log('argsToPass', argsToPass)
1412
1451
 
1413
- const relativePath = trimSurroundingQuotes(matchedFileString
1414
- .replace(fileRefSyntax, (match, varName) => varName.trim())
1415
- .replace('~', os.homedir()))
1416
-
1452
+ const relativePath = trimSurroundingQuotes(
1453
+ matchedFileString.replace(fileRefSyntax, (match, varName) => varName.trim()).replace('~', os.homedir()),
1454
+ )
1417
1455
 
1418
- let fullFilePath = (path.isAbsolute(relativePath) ? relativePath : path.join(this.configPath, relativePath))
1456
+ let fullFilePath = path.isAbsolute(relativePath) ? relativePath : path.join(this.configPath, relativePath)
1419
1457
 
1420
1458
  // console.log('fullFilePath', fullFilePath)
1421
1459
 
@@ -1423,7 +1461,7 @@ Like so: \${${variableString}, "fallbackValue"\}.`)
1423
1461
  // Get real path to handle potential symlinks (but don't fatal error)
1424
1462
  fullFilePath = fs.realpathSync(fullFilePath)
1425
1463
 
1426
- // Only match files that are relative
1464
+ // Only match files that are relative
1427
1465
  } else if (relativePath.match(/\.\//)) {
1428
1466
  // TODO test higher parent refs
1429
1467
  const cleanName = path.basename(relativePath)
@@ -1491,15 +1529,14 @@ Check if your javascript is exporting a function that returns a value.`
1491
1529
  deepProperties = deepProperties.map((prop) => {
1492
1530
  return trim(prop)
1493
1531
  })
1494
- return this.getDeeperValue(deepProperties, valueToPopulateResolved)
1495
- .then((deepValueToPopulateResolved) => {
1496
- if (typeof deepValueToPopulateResolved === 'undefined') {
1497
- 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}".
1498
1535
  Check if your javascript is returning the correct data.`
1499
- return Promise.reject(new Error(errorMessage))
1500
- }
1501
- return Promise.resolve(deepValueToPopulateResolved)
1502
- })
1536
+ return Promise.reject(new Error(errorMessage))
1537
+ }
1538
+ return Promise.resolve(deepValueToPopulateResolved)
1539
+ })
1503
1540
  })
1504
1541
  }
1505
1542
 
@@ -1545,13 +1582,9 @@ Please use ":" to reference sub properties`
1545
1582
  }
1546
1583
  return Promise.resolve(valueToPopulate)
1547
1584
  }
1548
-
1549
- getDeepIndex(variableString) {
1550
- const deepIndexReplace = RegExp(/^deep:|(\.[^}]+)*$/g)
1551
- return variableString.replace(deepIndexReplace, '')
1552
- }
1553
1585
  getVariableFromDeep(variableString) {
1554
- const index = this.getDeepIndex(variableString)
1586
+ const index = variableString.replace(deepIndexReplacePattern, '')
1587
+ // const index = this.getDeepIndex(variableString)
1555
1588
  /*
1556
1589
  console.log('FIND INDEX', index)
1557
1590
  console.log(this.deep)
@@ -1559,17 +1592,17 @@ Please use ":" to reference sub properties`
1559
1592
  return this.deep[index]
1560
1593
  }
1561
1594
  getValueFromDeep(variableString) {
1562
- const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g)
1563
1595
  const variable = this.getVariableFromDeep(variableString)
1564
- const deepRef = variableString.replace(deepPrefixReplace, '')
1596
+ const deepRef = variableString.replace(deepPrefixReplacePattern, '')
1565
1597
  /*
1566
1598
  console.log("GET getValueFromDeep", variableString)
1567
1599
  console.log('deepRef', deepRef)
1568
1600
  console.log('getValueFromDeep variable', variable)
1569
1601
  /** */
1570
- let ret = this.populateValue({ value: variable })
1602
+ let ret = this.populateValue({ value: variable }, undefined, 'getValueFromDeep')
1571
1603
  // console.log('variable ret', ret)
1572
- if (deepRef.length) { // if there is a deep reference remaining
1604
+ if (deepRef.length) {
1605
+ // if there is a deep reference remaining
1573
1606
  ret = ret.then((result) => {
1574
1607
  // console.log('DEEP RESULT', result)
1575
1608
  if (isString(result.value) && result.value.match(this.variableSyntax)) {
@@ -1582,7 +1615,6 @@ Please use ":" to reference sub properties`
1582
1615
  }
1583
1616
  return ret
1584
1617
  }
1585
-
1586
1618
  makeDeepVariable(variable) {
1587
1619
  // console.log('MAKE DEEP', variable)
1588
1620
  let index = this.deep.findIndex((item) => variable === item)
@@ -1599,7 +1631,6 @@ Please use ":" to reference sub properties`
1599
1631
 
1600
1632
  return deepVar
1601
1633
  }
1602
-
1603
1634
  /**
1604
1635
  * Get a value that is within the given valueToPopulate. The deepProperties specify what value
1605
1636
  * to retrieve from the given valueToPopulate. The trouble is that anywhere along this chain a
@@ -1619,16 +1650,17 @@ Please use ":" to reference sub properties`
1619
1650
  console.log('deepProperties', deepProperties)
1620
1651
  console.log('valueToPopulate', valueToPopulate)
1621
1652
  /** */
1622
-
1623
1653
  const veryDeep = deepProperties.reduce(async (reducedValueParam, subProperty) => {
1624
1654
  let reducedValue = await reducedValueParam
1625
1655
  // console.log('reducedValue', reducedValue)
1626
1656
  // console.log(typeof reducedValue)
1627
1657
  // console.log('subProperty', `"${subProperty}"`)
1628
1658
 
1629
- if (isString(reducedValue) && reducedValue.match(deepRefSyntax)) { // build mode
1659
+ if (isString(reducedValue) && reducedValue.match(deepRefSyntax)) {
1660
+ // build mode
1630
1661
  reducedValue = appendDeepVariable(reducedValue, subProperty)
1631
- } else { // get mode
1662
+ } else {
1663
+ // get mode
1632
1664
  if (typeof reducedValue === 'undefined') {
1633
1665
  // was reducedValue = {}
1634
1666
  // Adding internal flag signals this value is unknown
@@ -1684,126 +1716,14 @@ Please use ":" to reference sub properties`
1684
1716
  // console.log("MISSING", variableString)
1685
1717
  // console.log(this.deep)
1686
1718
  // console.log(valueToPopulate)
1687
-
1688
1719
  const notFoundMsg = `No ${varType} found to satisfy the '\${${variableString}}' variable. Attempting fallback value`
1689
1720
  if (DEBUG) {
1690
1721
  console.log(notFoundMsg)
1691
1722
  }
1692
-
1693
1723
  // errors make fallbacks not function. throw new Error(errorMsg)
1694
1724
  }
1695
1725
  return valueToPopulate
1696
1726
  }
1697
1727
  }
1698
1728
 
1699
- function findNestedVariable(split, originalSource) {
1700
- return split.find((thing) => {
1701
- if (originalSource && typeof originalSource === 'string') {
1702
- return originalSource.indexOf(`\${${thing}}`) > -1
1703
- }
1704
- return false
1705
- })
1706
- }
1707
-
1708
- /**
1709
- * Get fallback variable string
1710
- * @param {array} split - array from split at comma
1711
- * @param {string} nestedVar - fallback variable to reconstruct variable string from
1712
- * @return {string} - returns new ${variable, string}
1713
- */
1714
- function getFallbackString(split, nestedVar) {
1715
- let isSet = false
1716
- const newVar = split.reduce((acc, curr) => {
1717
- if (curr === nestedVar || isSet) {
1718
- acc = acc.concat(curr)
1719
- isSet = true
1720
- }
1721
- return acc
1722
- }, []).join(', ')
1723
- const cleanC = `\${${newVar.replace(/^\${/, '').replace(/}$/, '')}}`
1724
- return cleanC
1725
- }
1726
-
1727
- function verifyVariable(variableString, valueObject, variableTypes, config) {
1728
- const isRealVariable = variableTypes.some((r, i) => {
1729
- if (r.match instanceof RegExp && variableString.match(r.match)) {
1730
- return true
1731
- } else if (typeof r.match === 'function') {
1732
- if (r.match(variableString, config, valueObject)) {
1733
- return true
1734
- }
1735
- }
1736
- return false
1737
- })
1738
- // If not found in variable resolvers and is missing a colon throw
1739
- if (!isRealVariable && variableString.match(/:/)) {
1740
- // console.log('variableString', variableString)
1741
- throw new Error(`
1742
- Variable \${${variableString}} is invalid variable syntax.
1743
- Value Path: ${(valueObject.path) ? valueObject.path.join('.') : 'na'}
1744
- Original Value: ${valueObject.originalSource}
1745
-
1746
- Remove or update the \${${variableString}} to fix
1747
- `)
1748
- }
1749
- return isRealVariable
1750
- }
1751
-
1752
- function encodeUnknown(v) {
1753
- return `>passthrough[_[${Buffer.from(v).toString('base64')}]_]`
1754
- }
1755
-
1756
- function decodeUnknown(rawValue) {
1757
- const x = findUnknownValues(rawValue)
1758
- let val = rawValue.replace(/>passthrough/g, '')
1759
- if (x.length) {
1760
- x.forEach(({ match, value }) => {
1761
- // console.log('match', match)
1762
- const decodedValue = Buffer.from(value, 'base64').toString('ascii')
1763
- // console.log('decodedValue', decodedValue)
1764
- val = val.replace(match, decodedValue)
1765
- })
1766
- }
1767
- return val
1768
- }
1769
-
1770
- function findUnknownValues(text) {
1771
- let matches
1772
- let links = []
1773
- while ((matches = base64WrapperRegex.exec(text)) !== null) {
1774
- if (matches.index === base64WrapperRegex.lastIndex) {
1775
- base64WrapperRegex.lastIndex++ // avoid infinite loops with zero-width matches
1776
- }
1777
- links.push({
1778
- match: matches[0],
1779
- value: matches[1]
1780
- })
1781
- }
1782
- return links
1783
- }
1784
-
1785
- function isPromise(obj) { // eslint-disable-line
1786
- return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
1787
- }
1788
-
1789
- // TODO fix argument parsing to handle commas
1790
- function splitCsv(str, splitter) {
1791
- const splitSyntax = splitter || ','
1792
- // Split at comma SPACE ", "
1793
- return str.split(splitSyntax).reduce((accum, curr) => {
1794
- if (accum.isConcatting) {
1795
- accum.soFar[accum.soFar.length - 1] += ',' + curr
1796
- } else {
1797
- accum.soFar.push(curr)
1798
- }
1799
- if (curr.split('"').length % 2 == 0) { // eslint-disable-line
1800
- accum.isConcatting = !accum.isConcatting
1801
- }
1802
- return accum
1803
- }, {
1804
- soFar: [],
1805
- isConcatting: false
1806
- }).soFar
1807
- }
1808
-
1809
1729
  module.exports = Configorama