configorama 0.5.1 → 0.5.3
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/README.md +63 -21
- package/cli.js +1 -0
- package/package.json +14 -14
- package/src/main.js +197 -50
- package/src/resolvers/valueFromEnv.js +1 -0
- package/src/resolvers/valueFromGit.js +2 -0
- package/src/resolvers/valueFromNumber.js +1 -0
- package/src/resolvers/valueFromOptions.js +2 -0
- package/src/utils/PromiseTracker.js +3 -2
- package/src/utils/cleanVariable.js +30 -2
- package/src/utils/cleanVariable.test.js +98 -0
- package/src/utils/find-nested-variables.js +227 -0
- package/src/utils/find-nested-variables.test.js +99 -0
- package/src/utils/splitByComma.js +57 -45
- package/src/utils/splitByComma.test.js +84 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { findNestedVariables } = require('./find-nested-variables')
|
|
1
2
|
/**
|
|
2
3
|
* Convert variable into string
|
|
3
4
|
* ${opt:foo} => 'opt:foo'
|
|
@@ -7,10 +8,37 @@
|
|
|
7
8
|
|
|
8
9
|
const fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
9
10
|
const funcRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
10
|
-
module.exports = function cleanVariable(match, variableSyntax, simple) {
|
|
11
|
-
|
|
11
|
+
module.exports = function cleanVariable(match, variableSyntax, simple, caller) {
|
|
12
|
+
//console.log(`Clean input [${caller}]`, match)
|
|
13
|
+
|
|
14
|
+
let varToClean = match
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
const nestedVar = findNestedVariables(match, variableSyntax)
|
|
18
|
+
console.log('nestedVarssss', nestedVar)
|
|
19
|
+
if (nestedVar.length > 1) {
|
|
20
|
+
const lastMatch = nestedVar[nestedVar.length - 1]
|
|
21
|
+
console.log(`Clean output nested [${caller}]`, lastMatch.variable)
|
|
22
|
+
return lastMatch.variable
|
|
23
|
+
console.log('lastMatch', lastMatch)
|
|
24
|
+
const matchIndex = lastMatch.varString.match(/__VAR_(\d+)__/)
|
|
25
|
+
console.log('matchIndex', matchIndex)
|
|
26
|
+
if (matchIndex) {
|
|
27
|
+
const index = parseInt(matchIndex[1])
|
|
28
|
+
console.log('index', index)
|
|
29
|
+
console.log('nestedVar', nestedVar)
|
|
30
|
+
console.log('nestedVar[index]', nestedVar[index])
|
|
31
|
+
varToClean = lastMatch.varString.replace(/__VAR_(\d+)__/g, nestedVar[index].fullMatch)
|
|
32
|
+
return varToClean
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// process.exit(1)
|
|
36
|
+
/** */
|
|
37
|
+
|
|
38
|
+
const clean = varToClean.replace(variableSyntax, (context, contents) => {
|
|
12
39
|
return contents.trim()
|
|
13
40
|
})
|
|
41
|
+
//console.log(`Clean output [${caller}]`, clean)
|
|
14
42
|
return clean
|
|
15
43
|
|
|
16
44
|
// Support for simple variable cleaning with no space tweaks
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const cleanVariable = require('./cleanVariable')
|
|
4
|
+
|
|
5
|
+
test('cleanVariable - simple variable syntax without nesting', () => {
|
|
6
|
+
// Setup
|
|
7
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
8
|
+
const match = '${opt:foo}'
|
|
9
|
+
|
|
10
|
+
// Execute
|
|
11
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
12
|
+
|
|
13
|
+
// Verify
|
|
14
|
+
assert.is(result, 'opt:foo')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test.skip('cleanVariable - with nested variables', () => {
|
|
18
|
+
// Setup
|
|
19
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
20
|
+
const match = '${var:${opt:stage}}'
|
|
21
|
+
const nestedVars = [
|
|
22
|
+
{ varString: 'var:__VAR_1__', fullMatch: '${opt:stage}' },
|
|
23
|
+
{ varString: 'opt:stage', fullMatch: '${opt:stage}' }
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
// Execute
|
|
27
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
28
|
+
|
|
29
|
+
// Verify
|
|
30
|
+
assert.is(result, 'var:${opt:stage}')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('cleanVariable - file reference with commas', () => {
|
|
34
|
+
// Setup
|
|
35
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
36
|
+
const match = '${file(path/to/file.yml, key1, key2)}'
|
|
37
|
+
|
|
38
|
+
// Execute
|
|
39
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
40
|
+
|
|
41
|
+
// Verify
|
|
42
|
+
assert.is(result, 'file(path/to/file.yml, key1, key2)')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('cleanVariable - function format', () => {
|
|
46
|
+
// Setup
|
|
47
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
48
|
+
const match = '${someFunc(param1, param2)}'
|
|
49
|
+
|
|
50
|
+
// Execute
|
|
51
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
52
|
+
|
|
53
|
+
// Verify
|
|
54
|
+
assert.is(result, 'someFunc(param1, param2)')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('cleanVariable - simple mode', () => {
|
|
58
|
+
// Setup
|
|
59
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
60
|
+
const match = '${opt:foo with spaces}'
|
|
61
|
+
|
|
62
|
+
// Execute
|
|
63
|
+
const result = cleanVariable(match, variableSyntax, true, 'test')
|
|
64
|
+
|
|
65
|
+
// Verify
|
|
66
|
+
assert.is(result, 'opt:foo with spaces', 'Should preserve spaces in simple mode')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('cleanVariable - whitespace handling outside quotes', () => {
|
|
70
|
+
// Setup
|
|
71
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
72
|
+
const match = '${empty, "fallback value with space"}'
|
|
73
|
+
|
|
74
|
+
// Execute
|
|
75
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
76
|
+
|
|
77
|
+
// Verify
|
|
78
|
+
assert.is(result, 'empty, "fallback value with space"')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test.skip('cleanVariable - complex nested variables', () => {
|
|
82
|
+
// Setup
|
|
83
|
+
const variableSyntax = /\${([^{}]+?)}/g
|
|
84
|
+
const match = '${var:${opt:${stage}}}'
|
|
85
|
+
const nestedVars = [
|
|
86
|
+
{ varString: 'var:__VAR_1__', fullMatch: '${opt:${stage}}' },
|
|
87
|
+
{ varString: 'opt:__VAR_2__', fullMatch: '${stage}' },
|
|
88
|
+
{ varString: 'stage', fullMatch: '${stage}' }
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
// Execute
|
|
92
|
+
const result = cleanVariable(match, variableSyntax, false, 'test')
|
|
93
|
+
|
|
94
|
+
// Verify
|
|
95
|
+
assert.is(result, 'var:${opt:${stage}}')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test.run()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processes nested variable interpolations in a string and collects all matches
|
|
3
|
+
* @param {string} input - The input string containing variable interpolations
|
|
4
|
+
* @param {boolean} debug - Whether to print debug information
|
|
5
|
+
* @returns {Array} Array of match objects containing full match and captured group
|
|
6
|
+
*/
|
|
7
|
+
function findNestedVariablesx(input, regex, debug = false) {
|
|
8
|
+
let str = input
|
|
9
|
+
let matches = []
|
|
10
|
+
let match
|
|
11
|
+
let iteration = 0
|
|
12
|
+
|
|
13
|
+
console.log('input', input)
|
|
14
|
+
|
|
15
|
+
if (debug) console.log(`Initial string: ${str}`)
|
|
16
|
+
|
|
17
|
+
// Process string until no more matches are found
|
|
18
|
+
while (true) {
|
|
19
|
+
iteration++
|
|
20
|
+
if (debug) console.log(`\nIteration ${iteration}:`)
|
|
21
|
+
|
|
22
|
+
// Reset regex index
|
|
23
|
+
regex.lastIndex = 0
|
|
24
|
+
|
|
25
|
+
// Find the next match
|
|
26
|
+
match = regex.exec(str)
|
|
27
|
+
if (!match) break
|
|
28
|
+
|
|
29
|
+
// Log match details if in debug mode
|
|
30
|
+
if (debug) {
|
|
31
|
+
console.log(`Match: ${match[0]}`)
|
|
32
|
+
console.log(`Captured group: ${match[1]}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Store the match
|
|
36
|
+
matches.push({
|
|
37
|
+
fullMatch: match[0],
|
|
38
|
+
variable: match[1],
|
|
39
|
+
order: iteration
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Replace the match with placeholder
|
|
43
|
+
str = str.replace(regex, `__REPLACED_${iteration - 1}__`)
|
|
44
|
+
if (debug) console.log(`After replacement: ${str}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Replace the `__REPLACED_${iteration - 1}__` with the original match
|
|
48
|
+
matches = matches.map((match, index) => {
|
|
49
|
+
const indexOfReplaced = match.fullMatch.match(/__REPLACED_(\d+)__/)
|
|
50
|
+
if (indexOfReplaced) {
|
|
51
|
+
const replacedIndex = parseInt(indexOfReplaced[1])
|
|
52
|
+
match.fullMatch = match.fullMatch.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
53
|
+
match.variable = match.variable.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
54
|
+
}
|
|
55
|
+
return match
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (debug) console.log(`\nTotal matches found: ${matches.length}`)
|
|
59
|
+
return matches
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Finds all nested variable interpolations in a string while preserving original syntax
|
|
64
|
+
*
|
|
65
|
+
* This function handles complex nested variables like:
|
|
66
|
+
* ${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}
|
|
67
|
+
*
|
|
68
|
+
* The returned matches will include:
|
|
69
|
+
* 1. innermost variables first (e.g., ${defaultStage})
|
|
70
|
+
* 2. middle variables next (e.g., ${opt:stage, ${defaultStage}})
|
|
71
|
+
* 3. outermost variables last (e.g., the entire expression)
|
|
72
|
+
*
|
|
73
|
+
* Each variable retains its original syntax even in nested form.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} input - The input string containing variable interpolations
|
|
76
|
+
* @param {RegExp} regex - The regex pattern to match variables
|
|
77
|
+
* @param {boolean} debug - Whether to print debug information
|
|
78
|
+
* @returns {Array} Array of match objects with fullMatch, variable, varString and other properties
|
|
79
|
+
*/
|
|
80
|
+
function findNestedVariables(input, regex, debug = false) {
|
|
81
|
+
// Create a copy of the input for replacement tracking
|
|
82
|
+
let workingString = input
|
|
83
|
+
// console.log('workingString', workingString)
|
|
84
|
+
// Store matches with their positions in the original string
|
|
85
|
+
let matches = []
|
|
86
|
+
// Track original positions and replacements
|
|
87
|
+
let replacements = []
|
|
88
|
+
let match
|
|
89
|
+
let iteration = 0
|
|
90
|
+
|
|
91
|
+
if (debug) console.log(`Initial string: ${input}`)
|
|
92
|
+
|
|
93
|
+
// First pass: Find all matches and create unique placeholders
|
|
94
|
+
while (true) {
|
|
95
|
+
iteration++
|
|
96
|
+
if (debug) console.log(`\nIteration ${iteration}:`)
|
|
97
|
+
|
|
98
|
+
// Reset regex index
|
|
99
|
+
regex.lastIndex = 0
|
|
100
|
+
|
|
101
|
+
// Find the next match in the working string
|
|
102
|
+
match = regex.exec(workingString)
|
|
103
|
+
if (!match) break
|
|
104
|
+
|
|
105
|
+
// Generate a unique placeholder
|
|
106
|
+
const placeholder = `__VAR_${iteration - 1}__`
|
|
107
|
+
|
|
108
|
+
// Store match details
|
|
109
|
+
const matchInfo = {
|
|
110
|
+
fullMatch: match[0],
|
|
111
|
+
variable: match[1],
|
|
112
|
+
order: iteration,
|
|
113
|
+
start: match.index,
|
|
114
|
+
end: match.index + match[0].length,
|
|
115
|
+
placeholder
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (debug) {
|
|
119
|
+
console.log(`Match: ${match[0]}`)
|
|
120
|
+
console.log(`Captured group: ${match[1]}`)
|
|
121
|
+
console.log(`Position: ${match.index}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
matches.push(matchInfo)
|
|
125
|
+
|
|
126
|
+
// Store replacement info
|
|
127
|
+
replacements.push({
|
|
128
|
+
original: match[0],
|
|
129
|
+
placeholder,
|
|
130
|
+
start: match.index,
|
|
131
|
+
end: match.index + match[0].length
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// Replace in working string (to find next match)
|
|
135
|
+
workingString = workingString.substring(0, match.index) +
|
|
136
|
+
placeholder +
|
|
137
|
+
workingString.substring(match.index + match[0].length)
|
|
138
|
+
|
|
139
|
+
if (debug) console.log(`After replacement: ${workingString}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (debug) console.log(`\nTotal matches found: ${matches.length}`)
|
|
143
|
+
|
|
144
|
+
// We need to store varString - the variable string with placeholders
|
|
145
|
+
for (let i = 0; i < matches.length; i++) {
|
|
146
|
+
// First match, the variable is the same as varString
|
|
147
|
+
if (i === 0) {
|
|
148
|
+
matches[i].varString = matches[i].variable
|
|
149
|
+
} else {
|
|
150
|
+
// For other matches, we need to copy the original variable with placeholders
|
|
151
|
+
matches[i].varString = matches[i].variable
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Second pass: Reconstruct each variable with original nested syntax
|
|
156
|
+
// We need to do this recursively to ensure all placeholders are replaced properly
|
|
157
|
+
function replaceAllPlaceholders(text, matchesArray) {
|
|
158
|
+
let result = text
|
|
159
|
+
let needsAnotherPass = false
|
|
160
|
+
|
|
161
|
+
// Replace all placeholders with their original matches
|
|
162
|
+
for (let i = 0; i < matchesArray.length; i++) {
|
|
163
|
+
const m = matchesArray[i]
|
|
164
|
+
if (result.includes(m.placeholder)) {
|
|
165
|
+
result = result.replace(
|
|
166
|
+
new RegExp(m.placeholder, 'g'),
|
|
167
|
+
m.fullMatch
|
|
168
|
+
)
|
|
169
|
+
needsAnotherPass = true
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// If we made replacements, we might need another pass to handle nested placeholders
|
|
174
|
+
if (needsAnotherPass) {
|
|
175
|
+
return replaceAllPlaceholders(result, matchesArray)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// For each match, reconstruct the original nested syntax
|
|
182
|
+
for (let i = 0; i < matches.length; i++) {
|
|
183
|
+
const currentMatch = matches[i]
|
|
184
|
+
|
|
185
|
+
// Skip if this match doesn't contain any placeholders
|
|
186
|
+
if (!currentMatch.fullMatch.includes('__VAR_') && !currentMatch.variable.includes('__VAR_')) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Reconstruct with all nested variables
|
|
191
|
+
currentMatch.fullMatch = replaceAllPlaceholders(currentMatch.fullMatch, matches)
|
|
192
|
+
currentMatch.variable = replaceAllPlaceholders(currentMatch.variable, matches)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (debug) {
|
|
196
|
+
console.log("\nReconstructed matches:")
|
|
197
|
+
matches.forEach((m, i) => {
|
|
198
|
+
console.log(`Match #${i+1} (order ${m.order}):`)
|
|
199
|
+
console.log(`Full: ${m.fullMatch}`)
|
|
200
|
+
console.log(`Variable: ${m.variable}`)
|
|
201
|
+
console.log(`VarString: ${m.varString}`)
|
|
202
|
+
console.log(`Placeholder: ${m.placeholder}`)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return matches
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
// // Test with the example
|
|
211
|
+
// const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
212
|
+
// const input = '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}'
|
|
213
|
+
|
|
214
|
+
// // Run the function with debug output
|
|
215
|
+
// const result = findNestedVariables(input, regex, true)
|
|
216
|
+
|
|
217
|
+
// // Display final result
|
|
218
|
+
// console.log("\nFinal result:")
|
|
219
|
+
// console.log(JSON.stringify(result, null, 2))
|
|
220
|
+
|
|
221
|
+
// module.exports = {
|
|
222
|
+
// findNestedVariables
|
|
223
|
+
// }
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
findNestedVariables
|
|
227
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const { test } = require('uvu');
|
|
2
|
+
const assert = require('uvu/assert');
|
|
3
|
+
const { findNestedVariables } = require('./find-nested-variables');
|
|
4
|
+
|
|
5
|
+
// Define the regex pattern as used in the main function
|
|
6
|
+
const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g;
|
|
7
|
+
|
|
8
|
+
test('findNestedVariables - simple variables', () => {
|
|
9
|
+
const input = '${simple}';
|
|
10
|
+
const result = findNestedVariables(input, regex);
|
|
11
|
+
|
|
12
|
+
assert.equal(result.length, 1);
|
|
13
|
+
assert.equal(result[0].fullMatch, '${simple}');
|
|
14
|
+
assert.equal(result[0].variable, 'simple');
|
|
15
|
+
assert.equal(result[0].order, 1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('findNestedVariables - complex variable with colon syntax', () => {
|
|
19
|
+
const input = '${opt:stage, dev}';
|
|
20
|
+
const result = findNestedVariables(input, regex);
|
|
21
|
+
|
|
22
|
+
assert.equal(result.length, 1);
|
|
23
|
+
assert.equal(result[0].fullMatch, '${opt:stage, dev}');
|
|
24
|
+
assert.equal(result[0].variable, 'opt:stage, dev');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('findNestedVariables - one level nesting', () => {
|
|
28
|
+
const input = '${file(./config.${stage}.json)}';
|
|
29
|
+
const result = findNestedVariables(input, regex);
|
|
30
|
+
|
|
31
|
+
assert.equal(result.length, 2);
|
|
32
|
+
// The innermost variable should be found first
|
|
33
|
+
assert.equal(result[0].fullMatch, '${stage}');
|
|
34
|
+
assert.equal(result[0].variable, 'stage');
|
|
35
|
+
// Then the outer variable
|
|
36
|
+
assert.equal(result[1].fullMatch, '${file(./config.${stage}.json)}');
|
|
37
|
+
assert.equal(result[1].variable, 'file(./config.${stage}.json)');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('findNestedVariables - two levels of nesting', () => {
|
|
41
|
+
const input = '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}';
|
|
42
|
+
const result = findNestedVariables(input, regex);
|
|
43
|
+
|
|
44
|
+
assert.equal(result.length, 3);
|
|
45
|
+
// Innermost first
|
|
46
|
+
assert.equal(result[0].fullMatch, '${defaultStage}');
|
|
47
|
+
assert.equal(result[0].variable, 'defaultStage');
|
|
48
|
+
// Middle next
|
|
49
|
+
assert.equal(result[1].fullMatch, '${opt:stage, ${defaultStage}}');
|
|
50
|
+
assert.equal(result[1].variable, 'opt:stage, ${defaultStage}');
|
|
51
|
+
// Outermost last
|
|
52
|
+
assert.equal(result[2].fullMatch, '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}');
|
|
53
|
+
assert.equal(result[2].variable, 'file(./config.${opt:stage, ${defaultStage}}.json):CREDS');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('findNestedVariables - multiple separate variables', () => {
|
|
57
|
+
const input = 'Hello ${name}, welcome to ${service}!';
|
|
58
|
+
const result = findNestedVariables(input, regex);
|
|
59
|
+
|
|
60
|
+
assert.equal(result.length, 2);
|
|
61
|
+
assert.equal(result[0].fullMatch, '${name}');
|
|
62
|
+
assert.equal(result[1].fullMatch, '${service}');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('findNestedVariables - complex mixed case', () => {
|
|
66
|
+
const input = '${db.${envOne}.host}:${db.${envTwo}.port} using ${credentials.${user.role}}';
|
|
67
|
+
const result = findNestedVariables(input, regex, true);
|
|
68
|
+
console.log('result', result)
|
|
69
|
+
assert.equal(result.length, 6);
|
|
70
|
+
// Check the correct nesting order
|
|
71
|
+
assert.equal(result[0].fullMatch, '${envOne}');
|
|
72
|
+
assert.equal(result[1].fullMatch, '${db.${envOne}.host}');
|
|
73
|
+
assert.equal(result[2].fullMatch, '${envTwo}');
|
|
74
|
+
assert.equal(result[3].fullMatch, '${db.${envTwo}.port}');
|
|
75
|
+
assert.equal(result[4].fullMatch, '${user.role}');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('findNestedVariables - empty string', () => {
|
|
79
|
+
const input = '';
|
|
80
|
+
const result = findNestedVariables(input, regex);
|
|
81
|
+
assert.equal(result.length, 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('findNestedVariables - string with no variables', () => {
|
|
85
|
+
const input = 'This is a string with no variables';
|
|
86
|
+
const result = findNestedVariables(input, regex);
|
|
87
|
+
assert.equal(result.length, 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('findNestedVariables - varString property for nested variables', () => {
|
|
91
|
+
const input = '${file(./config.${opt:stage, ${defaultStage}}.json)}';
|
|
92
|
+
const result = findNestedVariables(input, regex);
|
|
93
|
+
console.log('result', result)
|
|
94
|
+
// Check varString property for the outermost variable
|
|
95
|
+
assert.equal(result[2].variable, 'file(./config.${opt:stage, ${defaultStage}}.json)');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Run all tests
|
|
99
|
+
test.run();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const stringResolver = require('../resolvers/valueFromString')
|
|
2
2
|
const overwriteSyntax = RegExp(/\s*(?:,\s*)+/g) // /\s*(?:,\s*)+/g
|
|
3
|
-
const stringRefSyntax = match
|
|
3
|
+
const stringRefSyntax = stringResolver.match
|
|
4
4
|
/**
|
|
5
5
|
* Split a given string by whitespace padded commas excluding those within single or double quoted
|
|
6
6
|
* strings.
|
|
@@ -14,56 +14,68 @@ const stringRefSyntax = match
|
|
|
14
14
|
// https://regex101.com/r/4uPmpt/1
|
|
15
15
|
const commasOutsideOfParens = /(?!<(?:\(|\[)[^)\]]+),(?![^(\[]+(?:\)|\]))/
|
|
16
16
|
// const commasOutOfParens = /(?!(?:\()[^)\]]+),(?![^(\[]+(?:\)))/g
|
|
17
|
-
function splitByComma(string) {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!string.match(commasOutsideOfParens)) {
|
|
22
|
-
return [ string ]
|
|
17
|
+
function splitByComma(string, regexPattern) {
|
|
18
|
+
// Handle empty or undefined input
|
|
19
|
+
if (!string || string.trim() === "") {
|
|
20
|
+
return [""]
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
|
|
23
|
+
// Extract regex patterns to protect them
|
|
24
|
+
const placeholders = []
|
|
25
|
+
let protectedString = string
|
|
26
|
+
if (regexPattern) {
|
|
27
|
+
protectedString = string.replace(regexPattern, (match) => {
|
|
28
|
+
placeholders.push(match)
|
|
29
|
+
return `__PLACEHOLDER_${placeholders.length - 1}__`
|
|
31
30
|
})
|
|
32
|
-
match = stringRefSyntax.exec(input)
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
|
|
33
|
+
const result = []
|
|
34
|
+
let current = ""
|
|
35
|
+
let inQuote = false
|
|
36
|
+
let quoteChar = ""
|
|
37
|
+
let bracketDepth = 0 // Includes both () and []
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < protectedString.length; i++) {
|
|
40
|
+
const char = protectedString[i]
|
|
41
|
+
|
|
42
|
+
// Handle quotes
|
|
43
|
+
if ((char === "'" || char === '"') && (i === 0 || protectedString[i-1] !== "\\")) {
|
|
44
|
+
if (!inQuote) {
|
|
45
|
+
inQuote = true
|
|
46
|
+
quoteChar = char
|
|
47
|
+
} else if (char === quoteChar) {
|
|
48
|
+
inQuote = false
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
|
|
52
|
+
// Handle parentheses and brackets
|
|
53
|
+
if ((char === "(" || char === "[") && !inQuote) bracketDepth++
|
|
54
|
+
if ((char === ")" || char === "]") && !inQuote) bracketDepth--
|
|
55
|
+
|
|
56
|
+
// Process comma
|
|
57
|
+
if (char === "," && !inQuote && bracketDepth === 0) {
|
|
58
|
+
result.push(current.trim())
|
|
59
|
+
current = ""
|
|
60
|
+
} else {
|
|
61
|
+
current += char
|
|
49
62
|
}
|
|
50
|
-
match = overwriteSyntax.exec(input)
|
|
51
63
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
|
|
65
|
+
if (current.trim() || result.length > 0) {
|
|
66
|
+
result.push(current.trim())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!regexPattern) {
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Restore placeholders in the result
|
|
74
|
+
return result.map(item => {
|
|
75
|
+
return item.replace(/__PLACEHOLDER_(\d+)__/g, (match, index) => {
|
|
76
|
+
return placeholders[parseInt(index)]
|
|
77
|
+
})
|
|
57
78
|
})
|
|
58
|
-
// const what = input.slice(prior)
|
|
59
|
-
// // TODO finish digit string matching
|
|
60
|
-
// const matchDigitString = /^['|"]\d+['|"]$/g
|
|
61
|
-
// if (what.match(matchDigitString)) {
|
|
62
|
-
// console.log('Is digit string')
|
|
63
|
-
// }
|
|
64
|
-
// console.log('what', what)
|
|
65
|
-
results.push(input.slice(prior))
|
|
66
|
-
return results
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
module.exports = {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const { splitByComma } = require('./splitByComma')
|
|
4
|
+
|
|
5
|
+
const variableSyntax = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
6
|
+
|
|
7
|
+
test('splitByComma - should return array with original string when no commas present', () => {
|
|
8
|
+
const result = splitByComma('singleString')
|
|
9
|
+
assert.equal(result, ['singleString'])
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('splitByComma - should split by comma and trim whitespace', () => {
|
|
13
|
+
const result = splitByComma('first, second, third')
|
|
14
|
+
assert.equal(result, ['first', 'second', 'third'])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('splitByComma - should preserve quoted strings', () => {
|
|
18
|
+
const result = splitByComma("env:BAZ,'defaultEnvValue'")
|
|
19
|
+
assert.equal(result, ["env:BAZ", "'defaultEnvValue'"])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('splitByComma - should not split commas inside parentheses', () => {
|
|
23
|
+
const result = splitByComma('file(file.js, param, paramTwo)')
|
|
24
|
+
assert.equal(result, ['file(file.js, param, paramTwo)'])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('splitByComma - should not split commas inside square brackets', () => {
|
|
28
|
+
const result = splitByComma('array[item1, item2, item3]')
|
|
29
|
+
assert.equal(result, ['array[item1, item2, item3]'])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('splitByComma - should handle multiple quoted strings', () => {
|
|
33
|
+
const result = splitByComma("'string one', 'string, with comma', \"double quoted\"")
|
|
34
|
+
assert.equal(result, ["'string one'", "'string, with comma'", "\"double quoted\""])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('splitByComma - should handle empty input', () => {
|
|
38
|
+
const result = splitByComma('')
|
|
39
|
+
assert.equal(result, [''])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('splitByComma - should handle input with extra whitespace', () => {
|
|
43
|
+
const result = splitByComma(' first , second , third ')
|
|
44
|
+
assert.equal(result, ['first', 'second', 'third'])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('splitByComma - should handle mixed scenarios', () => {
|
|
48
|
+
const result = splitByComma("normal, 'quoted, string', function(param1, param2)")
|
|
49
|
+
console.log('result', result)
|
|
50
|
+
assert.equal(result, ["normal", "'quoted, string'", "function(param1, param2)"])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('splitByComma - should handle nested parentheses', () => {
|
|
54
|
+
const result = splitByComma('outer(inner(a, b), c)')
|
|
55
|
+
assert.equal(result, ['outer(inner(a, b), c)'])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('splitByComma - should handle nested square brackets', () => {
|
|
59
|
+
const result = splitByComma('matrix[[1, 2], [3, 4]]')
|
|
60
|
+
assert.equal(result, ['matrix[[1, 2], [3, 4]]'])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('splitByComma - should handle combination of parentheses and brackets', () => {
|
|
64
|
+
const result = splitByComma('func(array[1, 2], obj.method(a, b))')
|
|
65
|
+
assert.equal(result, ['func(array[1, 2], obj.method(a, b))'])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('splitByComma - should handle escaped quotes in strings', () => {
|
|
69
|
+
const result = splitByComma("normal, 'string with \\'escaped\\' quotes', \"double \\\"escaped\\\" quotes\"")
|
|
70
|
+
assert.equal(result, ["normal", "'string with \\'escaped\\' quotes'", "\"double \\\"escaped\\\" quotes\""])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('splitByComma - should handle complex nested structures', () => {
|
|
74
|
+
const result = splitByComma('func(arg1, {key: [1, 2], other: "value, with comma"}, callback())')
|
|
75
|
+
assert.equal(result, ['func(arg1, {key: [1, 2], other: "value, with comma"}, callback())'])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('splitByComma - should handle backtick quotes', () => {
|
|
79
|
+
const result = splitByComma("normal, `template ${with, commas} inside`", variableSyntax)
|
|
80
|
+
assert.equal(result, ["normal", "`template ${with, commas} inside`"])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Run all tests
|
|
84
|
+
test.run()
|