1 |
|
2 | 'use strict';
|
3 | var async = require('async');
|
4 |
|
5 | var constants = {
|
6 | PKG_NAME: "apom",
|
7 | };
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | var _ = {
|
17 | isObjClass : function(obj, clas) {
|
18 | return Object.prototype.toString.call(obj) === clas;
|
19 | },
|
20 | isObject: function(obj) {
|
21 |
|
22 | return obj instanceof Object;
|
23 | },
|
24 | isObjectLiteral: function(obj) {
|
25 | return this.isObjClass(obj, "[object Object]");
|
26 | },
|
27 | isString: function(obj) {
|
28 | return this.isObjClass(obj, "[object String]");
|
29 | },
|
30 | isUndefined: function(obj) {
|
31 | return this.isObjClass(obj, "[object Undefined]");
|
32 | },
|
33 | isArray: function(obj) {
|
34 | return this.isObjClass(obj, "[object Array]");
|
35 | },
|
36 | isRegExp: function(obj) {
|
37 | return this.isObjClass(obj, "[object RegExp]");
|
38 | },
|
39 | isNull: function(obj) {
|
40 | return this.isObjClass(obj, "[object Null]");
|
41 | },
|
42 | isFunction: function(obj) {
|
43 | return this.isObjClass(obj, "[object Function]");
|
44 | }
|
45 | };
|
46 |
|
47 | var optionsDefault = {
|
48 | regExpMatch: false,
|
49 | regExpIgnoreCase: false,
|
50 | regExpAnchorStart: false,
|
51 | regExpAnchorEnd: false,
|
52 |
|
53 | matchIfPObjPropMissing: false,
|
54 | matchIfTObjPropMissing: false,
|
55 |
|
56 | variablesAllowed: false,
|
57 | getVariables: undefined,
|
58 |
|
59 | variablesStartStr: '~',
|
60 | variablesEndStr: null,
|
61 |
|
62 | propMatchFn: null
|
63 | };
|
64 |
|
65 | function getObjectProperties(obj) {
|
66 |
|
67 |
|
68 | var props = [];
|
69 | objectPropertyMap(
|
70 | obj,
|
71 | function(err, propName, propValue, propFullPath) {
|
72 | props.push(propFullPath);
|
73 | }
|
74 | );
|
75 |
|
76 | return props;
|
77 | }
|
78 |
|
79 | function makeMatchFn(props, options) {
|
80 |
|
81 | var propMatchFunctions = getPropMatchFns(props, options);
|
82 |
|
83 | return function(pObj, tObj, cb) {
|
84 | return matches(pObj, tObj, propMatchFunctions, cb);
|
85 | };
|
86 | }
|
87 |
|
88 | function getPropMatchFns(props, options) {
|
89 |
|
90 |
|
91 | var propMatchFunctions = [];
|
92 |
|
93 | getPropDefns(props, options)
|
94 | .forEach(function(propDefn) {
|
95 | makePropMatchFn(propDefn, options, function(err, fn){
|
96 | propMatchFunctions.push(fn);
|
97 | });
|
98 | });
|
99 | return propMatchFunctions;
|
100 | }
|
101 |
|
102 | function matches(pObj, tObj, propMatchFunctions, cb) {
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | if (_.isFunction(propMatchFunctions)) {
|
108 | cb = propMatchFunctions;
|
109 | propMatchFunctions =
|
110 | getPropMatchFns(getObjectProperties(pObj), {});
|
111 | }
|
112 |
|
113 | return async.every(
|
114 | propMatchFunctions,
|
115 | function(propMatchFunction, cb) {
|
116 | propMatchFunction(pObj, tObj, cb);
|
117 | },
|
118 | function(result) {
|
119 |
|
120 | return cb(result);
|
121 | }
|
122 | );
|
123 | }
|
124 |
|
125 | function setOptionsDefault(options, optionsDefault) {
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | if (options) {
|
131 | var optionsDefaultKeys = Object.keys(optionsDefault);
|
132 |
|
133 |
|
134 | for (var i = optionsDefaultKeys.length - 1; i >= 0; i--) {
|
135 | options[optionsDefaultKeys[i]] =
|
136 | options.hasOwnProperty(optionsDefaultKeys[i]) ?
|
137 | options[optionsDefaultKeys[i]] :
|
138 | optionsDefault[optionsDefaultKeys[i]];
|
139 | }
|
140 | } else {
|
141 | options = optionsDefault;
|
142 | }
|
143 |
|
144 | return options;
|
145 | }
|
146 |
|
147 | function getPropDefns(props, options) {
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | var propDefns;
|
170 |
|
171 |
|
172 | options = setOptionsDefault(options, optionsDefault);
|
173 |
|
174 |
|
175 | if(_.isObjectLiteral(props)) {
|
176 | var propNames = Object.keys(props);
|
177 | propDefns = [];
|
178 | for (var i = propNames.length - 1; i >= 0; i--) {
|
179 |
|
180 | props[propNames[i]].name = props[propNames[i]].name || propNames[i];
|
181 | propDefns.push(props[propNames[i]]);
|
182 | }
|
183 | } else {
|
184 | propDefns = props;
|
185 | }
|
186 |
|
187 |
|
188 | for (var j = propDefns.length - 1; j >= 0; j--) {
|
189 | if(_.isString(propDefns[j])) {
|
190 | var propDefn = {};
|
191 | propDefn.name = propDefns[j];
|
192 | propDefn = setOptionsDefault(propDefn, options);
|
193 | propDefns[j] = propDefn;
|
194 | } else if(_.isObjectLiteral(propDefns[j]) &&
|
195 | _.isString(propDefns[j].name)) {
|
196 | propDefns[j] = setOptionsDefault(propDefns[j], options);
|
197 | } else {
|
198 | throw new Error("Invalid parameter of properties; must be either an array" +
|
199 | " of poperty names or an object-literal with property names as keys");
|
200 | }
|
201 | }
|
202 |
|
203 | return propDefns;
|
204 | }
|
205 |
|
206 | function makePropMatchFn(propDefn, options, callback) {
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | var matchFn = _.isNull(propDefn.propMatchFn) ?
|
214 | makeStandardPropMatchFn(propDefn, options) :
|
215 | propDefn.propMatchFn;
|
216 |
|
217 | function returnFn(pObj, tObj, cb) {
|
218 |
|
219 | getPropRefs(
|
220 | pObj,
|
221 | tObj,
|
222 | propDefn.name,
|
223 | function(err, propRefsObj){
|
224 | matchFn(
|
225 | propRefsObj.pObjProp,
|
226 | propRefsObj.tObjProp,
|
227 | cb);
|
228 | });
|
229 |
|
230 | }
|
231 |
|
232 | return callback(null, returnFn);
|
233 | }
|
234 |
|
235 | function makeStandardPropMatchFn(propDefn, options) {
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | var preMatchFns = [];
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | var matchTests = [];
|
249 |
|
250 | if(propDefn.variablesAllowed) {
|
251 | preMatchFns.push(makeReplaceVariableFn());
|
252 | }
|
253 |
|
254 | if(propDefn.regExpMatch) {
|
255 | matchTests.push(makeRegExpMatchTestFn());
|
256 | } else {
|
257 | matchTests.push(equalTest);
|
258 | }
|
259 |
|
260 | if(propDefn.matchIfPObjPropMissing) {
|
261 | matchTests.push(pObjPropMissingTest);
|
262 | }
|
263 |
|
264 | if(propDefn.matchIfTObjPropMissing) {
|
265 | matchTests.push(tObjPropMissingTest);
|
266 | }
|
267 |
|
268 | var returnFn = preMatchFns.length === 0 ?
|
269 | standardMatchFn : standardMatchWithPreMatchFn;
|
270 |
|
271 | return returnFn;
|
272 |
|
273 | function standardMatchFn(pObjProp, tObjProp, cb) {
|
274 | async.some(
|
275 | matchTests,
|
276 | function(matchTest, cb) {
|
277 | return matchTest(pObjProp, tObjProp, cb);
|
278 | },
|
279 | function(matches) {
|
280 | return cb(matches);
|
281 | }
|
282 | );
|
283 | }
|
284 |
|
285 | function standardMatchWithPreMatchFn(pObjProp, tObjProp, cb) {
|
286 |
|
287 |
|
288 | var preMatchExecFns = [
|
289 | function(cb) {
|
290 | return cb(null, pObjProp, tObjProp);
|
291 | }].concat(preMatchFns);
|
292 |
|
293 | async.waterfall(
|
294 | preMatchExecFns,
|
295 | function(err, pObjProp, tObjProp) {
|
296 | async.some(
|
297 | matchTests,
|
298 | function(matchTest, cb) {
|
299 | return matchTest(pObjProp, tObjProp, cb);
|
300 | },
|
301 | function(matches) {
|
302 | return cb(matches);
|
303 | }
|
304 | );
|
305 | }
|
306 | );
|
307 | }
|
308 |
|
309 |
|
310 | function makeRegExpMatchTestFn() {
|
311 | var flags = "";
|
312 | var anchorStart = propDefn.regExpAnchorStart === true ? "^" : "";
|
313 | var anchorEnd = propDefn.regExpAnchorEnd === true ? "$" : "";
|
314 |
|
315 | flags = propDefn.regExpIgnoreCase === true ? flags.concat("i") : flags;
|
316 |
|
317 | function regExpMatchTestFn(pObjProp, tObjProp, cb) {
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | var re = _.isRegExp(pObjProp.value) ?
|
325 | pObjProp.value :
|
326 | new RegExp
|
327 | (anchorStart + pObjProp.value + anchorEnd, flags);
|
328 | return cb(pObjProp.exists &&
|
329 | re.test(tObjProp.value));
|
330 | }
|
331 |
|
332 | return regExpMatchTestFn;
|
333 | }
|
334 |
|
335 |
|
336 | function equalTest(pObjProp, tObjProp, cb) {
|
337 | return cb(pObjProp.exists &&
|
338 | pObjProp.value === tObjProp.value);
|
339 | }
|
340 |
|
341 | function pObjPropMissingTest(pObjProp, tObjProp, cb) {
|
342 | return cb(!pObjProp.exists);
|
343 | }
|
344 |
|
345 | function tObjPropMissingTest(pObjProp, tObjProp, cb) {
|
346 | return cb(!tObjProp.exists);
|
347 | }
|
348 |
|
349 |
|
350 | function makeReplaceVariableFn() {
|
351 | var variablesStartStr = escapeStr(propDefn.variablesStartStr);
|
352 | var variablesEndStr;
|
353 | var variableNameMatchRegExp;
|
354 |
|
355 | if(_.isNull(propDefn.variablesEndStr) ) {
|
356 | variableNameMatchRegExp = new RegExp(variablesStartStr + '(\\w*)',"g");
|
357 | } else {
|
358 | variablesEndStr = escapeStr(propDefn.variablesEndStr);
|
359 | variableNameMatchRegExp =
|
360 | new RegExp(
|
361 | variablesStartStr +
|
362 | '(.*)' +
|
363 | variablesEndStr,"g");
|
364 | }
|
365 |
|
366 | return function(pObjProp, tObjProp,cb) {
|
367 | propDefn.getVariables(function(err, variables) {
|
368 | replaceVariable(
|
369 | pObjProp,
|
370 | tObjProp,
|
371 | variables,
|
372 | function(err, pObjProp, tObjProp) {
|
373 | return cb(err, pObjProp, tObjProp);
|
374 | });
|
375 | });
|
376 | };
|
377 |
|
378 | function escapeStr(str) {
|
379 |
|
380 | return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
381 | }
|
382 |
|
383 | function replaceVariable(pObjProp, tObjProp, variables, cb) {
|
384 |
|
385 | if(pObjProp.exists && _.isString(pObjProp.value)) {
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 | var varName =
|
394 | pObjProp.value.replace(new RegExp("^" + variablesStartStr), "");
|
395 | varName = varName.replace(new RegExp(variablesEndStr + "$"), "");
|
396 | if (variables.hasOwnProperty(varName)) {
|
397 | pObjProp.value = variables[varName];
|
398 | } else if (_.isString(pObjProp.value)) {
|
399 | pObjProp.value = pObjProp.value
|
400 | .replace(variableNameMatchRegExp,getVariableValue);
|
401 | }
|
402 | }
|
403 | return cb(null, pObjProp, tObjProp);
|
404 |
|
405 | function getVariableValue(match, variableName) {
|
406 | return variables[variableName];
|
407 | }
|
408 |
|
409 | }
|
410 | }
|
411 |
|
412 | }
|
413 |
|
414 | function getPropRefs (pObj, tObj, propName, callback) {
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 | var propNameKeys = propName.split(".");
|
429 |
|
430 | var propRefsObj = {pObjProp: {}, tObjProp: {}};
|
431 |
|
432 | getPropRef(pObj, propNameKeys, function(err, pObjPropRef) {
|
433 | getPropRef(tObj, propNameKeys, function(err, tObjPropRef) {
|
434 | propRefsObj.pObjProp = pObjPropRef;
|
435 | propRefsObj.tObjProp = tObjPropRef;
|
436 | return callback(err, propRefsObj);
|
437 | });
|
438 | });
|
439 |
|
440 | }
|
441 |
|
442 |
|
443 | function getPropRef (obj, propNameKeys, callback) {
|
444 |
|
445 |
|
446 | var propRef = obj;
|
447 | var propExists;
|
448 | var i = 0;
|
449 | var len = propNameKeys.length;
|
450 | var propRefObj = {value: null, exists: null};
|
451 |
|
452 | async.whilst(
|
453 | function() {
|
454 | return _.isObject(propRef) &&
|
455 | propRef.hasOwnProperty(propNameKeys[i]) &&
|
456 | i < len;
|
457 | },
|
458 | function(cb) {
|
459 | propRef = propRef[propNameKeys[i]];
|
460 | i++;
|
461 | return cb(null);
|
462 | },
|
463 | function(err) {
|
464 |
|
465 | propExists = i === len ? true : false;
|
466 | propRefObj.value = propRef;
|
467 | propRefObj.exists = propExists;
|
468 | return callback(null, propRefObj);
|
469 | }
|
470 | );
|
471 |
|
472 | }
|
473 |
|
474 | function objectPropertyMap(obj, fullPropPath, cb) {
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 | if(_.isFunction(fullPropPath)) {
|
483 | cb = fullPropPath;
|
484 | fullPropPath = "";
|
485 | }
|
486 |
|
487 | var props = Object.keys(obj);
|
488 |
|
489 | for (var i = 0; i <= props.length -1; i++) {
|
490 | var newFullPropPath =
|
491 | fullPropPath === "" ?
|
492 | props[i] :
|
493 | fullPropPath + "." + props[i];
|
494 | if(_.isObjectLiteral(obj[props[i]])) {
|
495 | objectPropertyMap(obj[props[i]], newFullPropPath, cb);
|
496 | } else {
|
497 | cb(null, props[i], obj[props[i]], newFullPropPath);
|
498 | }
|
499 | }
|
500 | }
|
501 |
|
502 | function makeFilterPatternObjectsFn(props, options) {
|
503 |
|
504 | var propMatchFns = getPropMatchFns(props, options);
|
505 |
|
506 | return function(pObjs, tObj,cb) {
|
507 | return filterPatternObjects(pObjs, tObj, propMatchFns, cb);
|
508 | };
|
509 | }
|
510 |
|
511 | function filterPatternObjects(pObjs, tObj, propMatchFns, cb) {
|
512 | if (_.isFunction(propMatchFns)) {
|
513 | cb = propMatchFns;
|
514 | propMatchFns =
|
515 | getPropMatchFns(getObjectProperties(pObjs[0]), {regExpMatch: true});
|
516 | }
|
517 |
|
518 | return async.filter(
|
519 | pObjs,
|
520 | function(pObj, cb) {
|
521 | matches(
|
522 | pObj,
|
523 | tObj,
|
524 | propMatchFns,
|
525 | function(doesMatch){
|
526 | return cb(doesMatch);
|
527 | });
|
528 | },
|
529 | function(result) {
|
530 | return cb(result);
|
531 | }
|
532 | );
|
533 | }
|
534 |
|
535 | function makeFilterTargetObjectsFn(props, options) {
|
536 |
|
537 | var propMatchFns = getPropMatchFns(props, options);
|
538 |
|
539 | return function(pObj, tObjs,cb) {
|
540 | return filterTargetObjects(pObj, tObjs, propMatchFns, cb);
|
541 | };
|
542 | }
|
543 |
|
544 | function filterTargetObjects(pObj, tObjs, propMatchFns, cb) {
|
545 | if (_.isFunction(propMatchFns)) {
|
546 | cb = propMatchFns;
|
547 | propMatchFns =
|
548 | getPropMatchFns(getObjectProperties(pObj), {regExpMatch: true});
|
549 | }
|
550 |
|
551 |
|
552 | return async.filter(
|
553 | tObjs,
|
554 | function(tObj, cb) {
|
555 | matches(
|
556 | pObj,
|
557 | tObj,
|
558 | propMatchFns,
|
559 | function(doesMatch){
|
560 | return cb(doesMatch);
|
561 | });
|
562 | },
|
563 | function(result) {
|
564 | return cb(result);
|
565 | }
|
566 | );
|
567 | }
|
568 |
|
569 |
|
570 | module.exports = {
|
571 | makeMatchFn: makeMatchFn,
|
572 | getPropDefns: getPropDefns,
|
573 | getPropMatchFns: getPropMatchFns,
|
574 | objectPropertyMap: objectPropertyMap,
|
575 | matches: matches,
|
576 | makeFilterTargetObjectsFn: makeFilterTargetObjectsFn,
|
577 | makeFilterPatternObjectsFn: makeFilterPatternObjectsFn,
|
578 | filterPatternObjects: filterPatternObjects,
|
579 | filterTargetObjects: filterTargetObjects,
|
580 | getObjectProperties: getObjectProperties,
|
581 | _: _
|
582 | };
|