git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

translator.js (4721B)


      1 /* A couple of utility methods */
      2 
      3 function each(obj, iter) {
      4   for (var key in obj) {
      5     if (obj.hasOwnProperty(key)) iter(key, obj[key]);
      6   }
      7 }
      8 
      9 function nextString(str) {
     10   return 'S' + (parseInt(str.substr(1), 36) + 1).toString(36);
     11 }
     12 
     13 /* End utility methods */
     14 
     15 function Translator(api, options) {
     16   var origLength = JSON.stringify(api, null, 2).length;
     17   var debugInfo = {flattened: {}, pruned: {}};
     18   var shapeName = 'S0';
     19   var shapeNameMap = {};
     20   var visitedShapes = {};
     21 
     22   function logResults() {
     23     console.log('** Generated', api.metadata.endpointPrefix + '-' +
     24       api.metadata.apiVersion +'.min.json' +
     25       (process.env.DEBUG ? ':' : ''));
     26 
     27     if (process.env.DEBUG) {
     28       var pruned = Object.keys(debugInfo.pruned);
     29       var flattened = Object.keys(debugInfo.flattened);
     30       var newLength = JSON.stringify(api, null, 2).length;
     31       console.log('- Pruned Shapes:', pruned.length);
     32       console.log('- Flattened Shapes:', flattened.length);
     33       console.log('- Remaining Shapes:', Object.keys(api.shapes).length);
     34       console.log('- Original Size:', origLength / 1024.0, 'kb');
     35       console.log('- Minified Size:', newLength / 1024.0, 'kb');
     36       console.log('- Size Saving:', (origLength - newLength) / 1024.0, 'kb');
     37       console.log('');
     38     }
     39   }
     40 
     41   function deleteTraits(obj) {
     42     if (!options.documentation) {
     43       delete obj.documentation;
     44       delete obj.documentationUrl;
     45       delete obj.errors;
     46       delete obj.min;
     47       delete obj.max;
     48       delete obj.pattern;
     49       delete obj['enum'];
     50       delete obj.box;
     51     }
     52   }
     53 
     54   function trackShapeDeclaration(ref) {
     55     if (ref.shape && !shapeNameMap[ref.shape]) {
     56       // found a shape declaration we have not yet visited.
     57       // assign a new generated name in the shapeNameMap & visit it
     58       var oldShapeName = ref.shape;
     59       ref.shape = shapeName = nextString(shapeName);
     60 
     61       visitedShapes[shapeName] = api.shapes[oldShapeName];
     62       shapeNameMap[oldShapeName] = {name: shapeName, refs: [ref]};
     63 
     64       traverseShapeRef(api.shapes[oldShapeName]);
     65     } else if (ref.shape && shapeNameMap[ref.shape]) {
     66       // we visited this shape before. keep track of this ref and rename
     67       // the referenced declaration to the generated name
     68       var map = shapeNameMap[ref.shape];
     69       map.refs.push(ref);
     70       ref.shape = map.name;        
     71     }
     72   }
     73 
     74   function pruneShapes() {
     75     // prune shapes visited only once or only have type specifiers
     76     each(shapeNameMap, function(name, map) {
     77       if (Object.keys(visitedShapes[map.name]).join() === 'type' &&
     78           ['structure','map','list'].indexOf(visitedShapes[map.name].type) < 0) {
     79         // flatten out the shape (only a scalar type property is on the shape)
     80         for (var i = 0; i < map.refs.length; i++) {
     81           var ref = map.refs[i];
     82           debugInfo.flattened[name] = true;
     83           delete ref.shape;
     84           ref.type = visitedShapes[map.name].type;
     85 
     86           // string type is default, don't need to specify this
     87           if (ref.type === 'string') delete ref.type;
     88         }
     89 
     90         // we flattened all refs, we can prune the shape too
     91         delete visitedShapes[map.name];
     92         debugInfo.pruned[name] = true;
     93       } else if (map.refs.length === 1) { // only visited once
     94         // merge shape data onto ref
     95         var shape = visitedShapes[map.name];
     96 
     97         for (var i = 0; i < map.refs.length; i++) {
     98           delete map.refs[i].shape;
     99           for (var prop in shape) {
    100             if (shape.hasOwnProperty(prop)) map.refs[i][prop] = shape[prop];
    101           }
    102         }
    103 
    104         // delete the visited shape
    105         delete visitedShapes[map.name];
    106         debugInfo.pruned[name] = true;
    107       }
    108     });
    109   }
    110 
    111   function traverseShapeRef(ref) {
    112     if (!ref) return;
    113 
    114     deleteTraits(ref);
    115 
    116     traverseShapeRef(ref.key); // for maps
    117     traverseShapeRef(ref.value); // for maps
    118     traverseShapeRef(ref.member); // for lists
    119 
    120     // for structures
    121     each(ref.members || {}, function(key, value) { traverseShapeRef(value); });
    122 
    123     // resolve shape declarations
    124     trackShapeDeclaration(ref);
    125   }
    126 
    127   function traverseOperation(op) {
    128     deleteTraits(op);
    129 
    130     delete op.name;
    131     if (op.http) {
    132       if (op.http.method === 'POST') delete op.http.method;
    133       if (op.http.requestUri === '/') delete op.http.requestUri;
    134       if (Object.keys(op.http).length === 0) delete op.http;
    135     }
    136 
    137     traverseShapeRef(op.input);
    138     traverseShapeRef(op.output);    
    139   }
    140 
    141   function traverseApi() {
    142     deleteTraits(api);
    143     each(api.operations, function(name, op) { traverseOperation(op); });
    144     api.shapes = visitedShapes;
    145   }
    146 
    147   traverseApi();
    148   pruneShapes();
    149   logResults();
    150   return api;
    151 }
    152 
    153 module.exports = Translator;