-
Notifications
You must be signed in to change notification settings - Fork 0
/
ConfigStore.js
288 lines (235 loc) · 11 KB
/
ConfigStore.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// ------------------------------------------------------
// Object Tools
//
// (c) 2014 PonyCode Corporation
// License to distribute freely
//
// Simple native object merge
//
// { a : { b : "value", c : "value" }} MERGE { a : { b : "replace", e : "new" }}
// - YIELDS -
// { a : { b : "replace", c : "value", e: "new" }}
//
// Primitive properties will be replace object properties
// Object properties will be merged with object properties at the same key
//
// ------------------------------------------------------
( function( undefined ) {
var arrayWrap = require('./array-wrap');
var _ = require("lodash");
function ConfigStore( options ){
this._trace = {};
this._data = {};
this._options = options || {};
if( this._options.maxListValueLength === undefined ) this._options.maxListValueLength = 60;
if( this._options.maxListDepth === undefined ) this._options.maxListDepth = 8;
}
ConfigStore.prototype.lock = function(){
function _freezeObject( object ){
if( object instanceof Buffer || object instanceof Function ) return; // Cannot freeze these
if( !Object.isFrozen(object) ) Object.freeze( object );
_.forOwn( object, function( value, key ){
if( _.isObject( value ) ){
_freezeObject( value );
}else if( _.isArray( value ) ){
_.each( value, function( val ){
_freezeObject( value );
});
}
});
}
_freezeObject( this._data );
};
ConfigStore.prototype.set = function( keyPath, value, traceSource ){
if( !keyPath || !keyPath.length ) return;
traceSource = traceSource || "?";
if( keyPath === '.' ){
this._data = this._merge( this._data, value, traceSource, "" );
} else if( keyPath.indexOf('.') > 0 ){
this._setValueForDottedKeyPath( this._data, value, keyPath.split('.'), traceSource, keyPath );
}else{
this._data[ keyPath ] = this._merge( this._data[ keyPath ], value, traceSource, keyPath );
}
};
ConfigStore.prototype.get = function( keyPath, defaultValue ){
if( !keyPath || !keyPath.length ) return undefined;
var value;
if( keyPath === '.' ){
value = this._data;
} else if( keyPath.indexOf('.') != -1 ){
value = this._getValueForDottedKeyPath( this._data, keyPath.split('.') );
}else{
value = this._data[ keyPath ];
}
return ( value === undefined ) ? defaultValue : value ;
};
ConfigStore.prototype.list = function( options ){
var self = this;
var chalk = require('chalk');
options = options || {};
if( options.maxListDepth === undefined ) options.maxListDepth = self._options.maxListDepth;
if( options.maxListValueLength === undefined ) options.maxListValueLenth = self._options.maxListValueLength;
if( options.maxListDepth === 0 ) return;
if( options.secure ) options.secure = arrayWrap.wrap( options.secure );
var outStream = options.outputStream || console.log;
var valueFormatter = ( options.formatter ) ? options.formatter : function(v){ return v; };
var dStr = '├───';
var depthString = '';
for( var i=0; i <= options.maxListDepth; i++) depthString += dStr;
function _isPrintableObject( object ){
if (object instanceof Function) return true;
if (object instanceof String) return true;
if (object instanceof Number) return true;
if (object instanceof Date) return true;
if (object instanceof RegExp) return true;
if (object instanceof Buffer) return true;
return false;
}
function _valueSecured( keyPath ){
if( !options.secure ) return false;
if( keyPath.indexOf('.') === 0 ) keyPath = keyPath.slice(1);
for( var i=0; i < options.secure.length; i++ ){
var secureProperty = options.secure[i];
if( secureProperty instanceof RegExp ){
if( keyPath.match( secureProperty ) !== null ) return true;
}else{
if( keyPath === secureProperty) return true;
}
}
return false;
}
function _recursiveListObject( object, path, depth ){
path = path || "";
depth = depth || 1; // the first set of config is at depth 1
var depthIndicator = depthString.substring(0,dStr.length*(depth));
for( var key in object ){
if( object.hasOwnProperty( key ) ){
var line = chalk.white( depthIndicator );
line += chalk.white.bold( key ) + chalk.white(' : ');
var value = object[key];
var fullKeyPath = path + "." + key;
var stringValue = false;
var stringValueTruncated = false;
var valueSecured = false;
var shouldRecurse = false;
var source = self.trace( fullKeyPath );
if( value === undefined ){
stringValue = 'undefined';
} else {
if( _valueSecured( fullKeyPath )){
shouldRecurse = false;
valueSecured = true;
} else {
if (value instanceof Object) {
if (_isPrintableObject(value)) {
stringValue = valueFormatter( value, fullKeyPath ).toString();
} else {
if (depth <= options.maxListDepth - 1){
shouldRecurse = true;
} else {
stringValue = chalk.yellow( '...' );
}
}
} else {
stringValue = valueFormatter( value, fullKeyPath ).toString();
}
}
}
if( stringValue && stringValue.length > options.maxListValueLength ){
stringValue = stringValue.substring( 0, options.maxListValueLength );
stringValueTruncated = true;
}
if( valueSecured ){
line += chalk.red( "*****" );
} else {
if( stringValue ) line += chalk.cyan( stringValue );
if( stringValueTruncated ) line += chalk.yellow(' ...');
}
if( source ) line += ' [' + chalk.yellow( source ) + ']';
outStream( line );
if( shouldRecurse ){
_recursiveListObject( value, fullKeyPath, depth+1);
}
}
}
}
_recursiveListObject( self._data );
};
ConfigStore.prototype.trace = function( key ){
if( key === undefined ) return this._trace;
return this._trace[ this._normalizedKey( key ) ];
};
ConfigStore.prototype._normalizedKey = function( key ){
return (key.charAt(0) !== '.' ) ? "." + key : key;
};
// ----------------------------
// Helper to merge two values (objects or primitives)
// ----------------------------
ConfigStore.prototype._merge = function( backObject, frontObject, sourceName, currentPath ){
currentPath = currentPath || "";
sourceName = sourceName || "?";
this._trace[ this._normalizedKey(currentPath) ] = sourceName;
if( backObject === undefined ) {
return frontObject;
}
if( Array.isArray( frontObject ) || (frontObject instanceof Buffer) || !(frontObject instanceof Object) ){ // primitive value returned as-is, array used without merging
return frontObject;
}
if( !(backObject instanceof Object) ){ // primitive values replaced
return frontObject;
}
var outObject = {};
var propertyName;
// Clone one level of backObject properties
for( propertyName in backObject ) {
if (backObject.hasOwnProperty(propertyName)) {
outObject[propertyName] = backObject[propertyName];
}
}
// Merge in each property of front object, Recursive
for( propertyName in frontObject ) {
if (frontObject.hasOwnProperty(propertyName)) {
outObject[ propertyName ] = this._merge( backObject[ propertyName], frontObject[ propertyName ], sourceName, currentPath + "." + propertyName);
}
}
return outObject;
};
// ----------------------------
// Helper to set config with a dot-path key
// ----------------------------
ConfigStore.prototype._setValueForDottedKeyPath = function( targetData, configValue, configKeyPathComponents, sourceName, keyPath ){
if( configKeyPathComponents.length === 1){
targetData[ configKeyPathComponents ] = this._merge( targetData[ configKeyPathComponents ], configValue, sourceName, keyPath );
} else {
var nextComponent = configKeyPathComponents.shift();
if( typeof targetData[nextComponent] === 'undefined' ){
targetData[nextComponent] = {};
} else if( typeof targetData[nextComponent] !== 'object' ){
throw new Error("Attempt to set value with path through non object at path: " + configKeyPathComponents );
}
this._setValueForDottedKeyPath( targetData[nextComponent], configValue, configKeyPathComponents, sourceName, keyPath );
}
};
// ----------------------------
// Helper to get config with a dot-path key
// ----------------------------
ConfigStore.prototype._getValueForDottedKeyPath = function( sourceData, configKeyPathComponents ){
if( configKeyPathComponents.length === 1 ){
return sourceData[configKeyPathComponents[0]];
}else{
var nextComponent = configKeyPathComponents.shift();
if( typeof sourceData[nextComponent] === 'undefined' ) return undefined;
if( typeof sourceData[nextComponent] !== 'object' ){
throw new Error("Attempt to get value with path through non object at sub-path: " + configKeyPathComponents );
}
return this._getValueForDottedKeyPath( sourceData[nextComponent], configKeyPathComponents );
}
};
// ----------------------------
// Expose merge objects, as a utility and for testing
// ----------------------------
ConfigStore.prototype.mergeObjects = function( backObject, frontObject ){
return this._merge( backObject, frontObject );
};
module.exports = ConfigStore;
})();