twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

util.js (8083B)


      1 'use strict';
      2 
      3 
      4 module.exports = {
      5   copy: copy,
      6   checkDataType: checkDataType,
      7   checkDataTypes: checkDataTypes,
      8   coerceToTypes: coerceToTypes,
      9   toHash: toHash,
     10   getProperty: getProperty,
     11   escapeQuotes: escapeQuotes,
     12   equal: require('fast-deep-equal'),
     13   ucs2length: require('./ucs2length'),
     14   varOccurences: varOccurences,
     15   varReplace: varReplace,
     16   cleanUpCode: cleanUpCode,
     17   finalCleanUpCode: finalCleanUpCode,
     18   schemaHasRules: schemaHasRules,
     19   schemaHasRulesExcept: schemaHasRulesExcept,
     20   schemaUnknownRules: schemaUnknownRules,
     21   toQuotedString: toQuotedString,
     22   getPathExpr: getPathExpr,
     23   getPath: getPath,
     24   getData: getData,
     25   unescapeFragment: unescapeFragment,
     26   unescapeJsonPointer: unescapeJsonPointer,
     27   escapeFragment: escapeFragment,
     28   escapeJsonPointer: escapeJsonPointer
     29 };
     30 
     31 
     32 function copy(o, to) {
     33   to = to || {};
     34   for (var key in o) to[key] = o[key];
     35   return to;
     36 }
     37 
     38 
     39 function checkDataType(dataType, data, negate) {
     40   var EQUAL = negate ? ' !== ' : ' === '
     41     , AND = negate ? ' || ' : ' && '
     42     , OK = negate ? '!' : ''
     43     , NOT = negate ? '' : '!';
     44   switch (dataType) {
     45     case 'null': return data + EQUAL + 'null';
     46     case 'array': return OK + 'Array.isArray(' + data + ')';
     47     case 'object': return '(' + OK + data + AND +
     48                           'typeof ' + data + EQUAL + '"object"' + AND +
     49                           NOT + 'Array.isArray(' + data + '))';
     50     case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
     51                            NOT + '(' + data + ' % 1)' +
     52                            AND + data + EQUAL + data + ')';
     53     default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
     54   }
     55 }
     56 
     57 
     58 function checkDataTypes(dataTypes, data) {
     59   switch (dataTypes.length) {
     60     case 1: return checkDataType(dataTypes[0], data, true);
     61     default:
     62       var code = '';
     63       var types = toHash(dataTypes);
     64       if (types.array && types.object) {
     65         code = types.null ? '(': '(!' + data + ' || ';
     66         code += 'typeof ' + data + ' !== "object")';
     67         delete types.null;
     68         delete types.array;
     69         delete types.object;
     70       }
     71       if (types.number) delete types.integer;
     72       for (var t in types)
     73         code += (code ? ' && ' : '' ) + checkDataType(t, data, true);
     74 
     75       return code;
     76   }
     77 }
     78 
     79 
     80 var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
     81 function coerceToTypes(optionCoerceTypes, dataTypes) {
     82   if (Array.isArray(dataTypes)) {
     83     var types = [];
     84     for (var i=0; i<dataTypes.length; i++) {
     85       var t = dataTypes[i];
     86       if (COERCE_TO_TYPES[t]) types[types.length] = t;
     87       else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
     88     }
     89     if (types.length) return types;
     90   } else if (COERCE_TO_TYPES[dataTypes]) {
     91     return [dataTypes];
     92   } else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
     93     return ['array'];
     94   }
     95 }
     96 
     97 
     98 function toHash(arr) {
     99   var hash = {};
    100   for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
    101   return hash;
    102 }
    103 
    104 
    105 var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
    106 var SINGLE_QUOTE = /'|\\/g;
    107 function getProperty(key) {
    108   return typeof key == 'number'
    109           ? '[' + key + ']'
    110           : IDENTIFIER.test(key)
    111             ? '.' + key
    112             : "['" + escapeQuotes(key) + "']";
    113 }
    114 
    115 
    116 function escapeQuotes(str) {
    117   return str.replace(SINGLE_QUOTE, '\\$&')
    118             .replace(/\n/g, '\\n')
    119             .replace(/\r/g, '\\r')
    120             .replace(/\f/g, '\\f')
    121             .replace(/\t/g, '\\t');
    122 }
    123 
    124 
    125 function varOccurences(str, dataVar) {
    126   dataVar += '[^0-9]';
    127   var matches = str.match(new RegExp(dataVar, 'g'));
    128   return matches ? matches.length : 0;
    129 }
    130 
    131 
    132 function varReplace(str, dataVar, expr) {
    133   dataVar += '([^0-9])';
    134   expr = expr.replace(/\$/g, '$$$$');
    135   return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
    136 }
    137 
    138 
    139 var EMPTY_ELSE = /else\s*{\s*}/g
    140   , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
    141   , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
    142 function cleanUpCode(out) {
    143   return out.replace(EMPTY_ELSE, '')
    144             .replace(EMPTY_IF_NO_ELSE, '')
    145             .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
    146 }
    147 
    148 
    149 var ERRORS_REGEXP = /[^v.]errors/g
    150   , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
    151   , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
    152   , RETURN_VALID = 'return errors === 0;'
    153   , RETURN_TRUE = 'validate.errors = null; return true;'
    154   , RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
    155   , RETURN_DATA_ASYNC = 'return data;'
    156   , ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
    157   , REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
    158 
    159 function finalCleanUpCode(out, async) {
    160   var matches = out.match(ERRORS_REGEXP);
    161   if (matches && matches.length == 2) {
    162     out = async
    163           ? out.replace(REMOVE_ERRORS_ASYNC, '')
    164                .replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
    165           : out.replace(REMOVE_ERRORS, '')
    166                .replace(RETURN_VALID, RETURN_TRUE);
    167   }
    168 
    169   matches = out.match(ROOTDATA_REGEXP);
    170   if (!matches || matches.length !== 3) return out;
    171   return out.replace(REMOVE_ROOTDATA, '');
    172 }
    173 
    174 
    175 function schemaHasRules(schema, rules) {
    176   if (typeof schema == 'boolean') return !schema;
    177   for (var key in schema) if (rules[key]) return true;
    178 }
    179 
    180 
    181 function schemaHasRulesExcept(schema, rules, exceptKeyword) {
    182   if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
    183   for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
    184 }
    185 
    186 
    187 function schemaUnknownRules(schema, rules) {
    188   if (typeof schema == 'boolean') return;
    189   for (var key in schema) if (!rules[key]) return key;
    190 }
    191 
    192 
    193 function toQuotedString(str) {
    194   return '\'' + escapeQuotes(str) + '\'';
    195 }
    196 
    197 
    198 function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
    199   var path = jsonPointers // false by default
    200               ? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
    201               : (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
    202   return joinPaths(currentPath, path);
    203 }
    204 
    205 
    206 function getPath(currentPath, prop, jsonPointers) {
    207   var path = jsonPointers // false by default
    208               ? toQuotedString('/' + escapeJsonPointer(prop))
    209               : toQuotedString(getProperty(prop));
    210   return joinPaths(currentPath, path);
    211 }
    212 
    213 
    214 var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
    215 var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
    216 function getData($data, lvl, paths) {
    217   var up, jsonPointer, data, matches;
    218   if ($data === '') return 'rootData';
    219   if ($data[0] == '/') {
    220     if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data);
    221     jsonPointer = $data;
    222     data = 'rootData';
    223   } else {
    224     matches = $data.match(RELATIVE_JSON_POINTER);
    225     if (!matches) throw new Error('Invalid JSON-pointer: ' + $data);
    226     up = +matches[1];
    227     jsonPointer = matches[2];
    228     if (jsonPointer == '#') {
    229       if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
    230       return paths[lvl - up];
    231     }
    232 
    233     if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
    234     data = 'data' + ((lvl - up) || '');
    235     if (!jsonPointer) return data;
    236   }
    237 
    238   var expr = data;
    239   var segments = jsonPointer.split('/');
    240   for (var i=0; i<segments.length; i++) {
    241     var segment = segments[i];
    242     if (segment) {
    243       data += getProperty(unescapeJsonPointer(segment));
    244       expr += ' && ' + data;
    245     }
    246   }
    247   return expr;
    248 }
    249 
    250 
    251 function joinPaths (a, b) {
    252   if (a == '""') return b;
    253   return (a + ' + ' + b).replace(/' \+ '/g, '');
    254 }
    255 
    256 
    257 function unescapeFragment(str) {
    258   return unescapeJsonPointer(decodeURIComponent(str));
    259 }
    260 
    261 
    262 function escapeFragment(str) {
    263   return encodeURIComponent(escapeJsonPointer(str));
    264 }
    265 
    266 
    267 function escapeJsonPointer(str) {
    268   return str.replace(/~/g, '~0').replace(/\//g, '~1');
    269 }
    270 
    271 
    272 function unescapeJsonPointer(str) {
    273   return str.replace(/~1/g, '/').replace(/~0/g, '~');
    274 }