Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / plugins / composer / src / schemas / yang / confd2model.js
diff --git a/skyquake/plugins/composer/src/schemas/yang/confd2model.js b/skyquake/plugins/composer/src/schemas/yang/confd2model.js
new file mode 100644 (file)
index 0000000..ecaddf4
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+'use strict';
+
+// the models to be transformed into the output DSL JSON meta file
+var yang = [require('./json-nsd.json'), require('./json-vnfd.json')];
+
+var _ = require('lodash');
+var inet = require('./ietf-inet-types.yang.json');
+
+var utils = {
+       resolvePath(obj, path) {
+               // supports a.b, a[1] and foo[bar], etc.
+               // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
+               // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
+               path = path.split(/[\.\[\]]/).filter(d => d);
+               return path.reduce((r, p) => {
+                       if (r) {
+                               return r[p];
+                       }
+               }, obj);
+       },
+       assignPathValue(obj, path, value) {
+               path = path.split(/[\.\[\]]/).filter(d => d);
+               // enable look-ahead to determine if type is array or object
+               const pathCopy = path.slice();
+               // last item in path used to assign value on the resolved object
+               const name = path.pop();
+               const resolvedObj = path.reduce((r, p, i) => {
+                       if (typeof(r[p]) !== 'object') {
+                               // look-ahead to see if next path item is a number
+                               const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
+                               r[p] = isArray ? [] : {}
+                       }
+                       return r[p];
+               }, obj);
+               resolvedObj[name] = value;
+       }
+};
+
+var isType = d => /^(leaf|leaf-list|list|container|choice|case|uses)$/.test(d);
+
+function deriveCardinalityFromProperty(property, typeName) {
+       if (String(property.mandatory) === 'true') {
+               return '1';
+       }
+       let min = 0, max = Infinity;
+       if (property.hasOwnProperty('min-elements')) {
+               min = parseInt(property['min-elements'], 10) || 0;
+       }
+       if (property.hasOwnProperty('max-elements')) {
+               max = parseInt(property['max-elements'], 10) || Infinity;
+       } else {
+               if (!/^(list|leaf-list)$/.test(typeName)) {
+                       max = '1';
+               }
+       }
+       if (min > max) {
+               return String(min);
+       }
+       if (min === max) {
+               return String(min);
+       }
+       return String(min) + '..' + (max === Infinity ? 'N' : max);
+}
+
+function cleanWhitespace(text) {
+       if (typeof text === 'string') {
+               return text.replace(/\s+/g, ' ');
+       }
+       return text;
+}
+
+function buildProperties(typeData, typeName) {
+       var properties = [];
+       Object.keys(typeData).forEach(name => {
+               var property = typeData[name];
+               var listKey = typeName === 'list' ? String(property.key).split(/\s/).filter(k => k && k !== 'undefined') : false;
+               var meta = {
+                       name: name,
+                       type: typeName,
+                       description: cleanWhitespace(property.description),
+                       cardinality: deriveCardinalityFromProperty(property, typeName),
+                       'data-type': property.type,
+                       properties: Object.keys(property).filter(isType).reduce((r, childType) => {
+                               return r.concat(buildProperties(property[childType], childType));
+                       }, [])
+               };
+               if (listKey) {
+                       meta.key = listKey;
+               }
+               properties.push(meta);
+       });
+       return properties;
+}
+
+function lookupUses(uses, yang) {
+       function doLookup(lookupTypeName) {
+               var key;
+               // warn: hardcoded prefix support for mano-types - other prefixes will be ignored
+               if (/^manotypes:/.test(lookupTypeName)) {
+                       var moduleName = lookupTypeName.split(':')[1];
+                       key = ['dependencies.mano-types.module.mano-types.grouping', moduleName].join('.');
+               } else {
+                       var name = yang.name.replace(/^rw-/, '');
+                       key = ['dependencies', name, 'module', name, 'grouping', lookupTypeName].join('.');
+               }
+               return utils.resolvePath(yang, key);
+       }
+       if (typeof uses === 'object') {
+               return Object.keys(uses).reduce((result, key) => {
+                       var found = doLookup(key);
+                       Object.keys(found).filter(isType).forEach(type => {
+                               var property = result[type] || (result[type] = {});
+                               Object.assign(property, found[type]);
+                       });
+                       return result;
+               }, {});
+       } else if (typeof uses === 'string') {
+               return doLookup(uses);
+       }
+       return {};
+}
+
+function lookupTypedef(property, yang) {
+       var key;
+       var lookupTypeName = property.type;
+       // warn: hardcoded prefix support - other prefixes will be ignored
+       if (/^manotypes:/.test(lookupTypeName)) {
+               var lookupName = lookupTypeName.split(':')[1];
+               key = ['dependencies.mano-types.module.mano-types.typedef', lookupName].join('.');
+       } else if (/^inet:/.test(lookupTypeName)) {
+               var lookupName = lookupTypeName.split(':')[1];
+               yang = inet;
+               key = ['schema.module.ietf-inet-types.typedef', lookupName].join('.');
+       }
+       if (key) {
+               return utils.resolvePath(yang, key);
+       }
+}
+
+function resolveUses(property, yang) {
+       var childData = property.uses;
+       var resolved = lookupUses(childData, yang);
+       //console.log('uses', childData, 'found', resolved);
+       Object.keys(resolved).forEach(type => {
+               var parentTypes = property[type] || (property[type] = {});
+               // copy types into the parent types bucket
+               Object.assign(parentTypes, resolveReferences(yang, resolved[type]));
+       });
+       delete property.uses;
+}
+
+function resolveTypedef(property, yang) {
+       if (/:/.test(property.type)) {
+               var found = lookupTypedef(property, yang);
+               if (found) {
+                       Object.assign(property, found);
+               }
+       }
+}
+
+function resolveReferences(yang, data) {
+       var dataClone = _.cloneDeep(data);
+       function doResolve(typeData) {
+               Object.keys(typeData).forEach(name => {
+                       var property = typeData[name];
+                       resolveTypedef(property, yang);
+                       Object.keys(property).filter(isType).forEach(childType => {
+                               if (childType === 'uses') {
+                                       resolveUses(property, yang);
+                               } else {
+                                       doResolve(property[childType]);
+                               }
+                       });
+               });
+       }
+       doResolve(dataClone);
+       return dataClone;
+}
+
+function module(yang) {
+       let module;
+       var name = yang.name.replace(/^rw-/, '');
+       if (!name) {
+               throw 'no name given in json yang';
+       }
+       const path = ['container', name + '-catalog'].join('.');
+       module = utils.resolvePath(yang, path);
+
+       if (!module) {
+               module = utils.resolvePath(yang, ['schema', 'module', name, path].join('.'));
+       }
+       if (!module) {
+               module = utils.resolvePath(yang, ['dependencies', name, 'module', name, path].join('.'));
+       }
+       if (!module) {
+               throw 'cannot find the module' + name;
+       }
+
+       // module/agument/nsd:nsd-catalog/nsd:nsd/meta
+       const augLeafPath = ['schema.module', 'rw-' + name, 'augment', '/' + name + ':' + name + '-catalog/' + name + ':' + name, 'leaf'];
+       const meta = utils.resolvePath(yang, augLeafPath.concat('meta').join('.'));
+
+       const putLeafPath = ['dependencies', name, 'module', name, path, 'list', name, 'leaf'];
+
+       if (meta) {
+               utils.assignPathValue(yang, putLeafPath.concat(['meta']).join('.'), meta);
+       }
+
+       // module/agument/nsd:nsd-catalog/nsd:nsd/logo
+       const logo = utils.resolvePath(yang, augLeafPath.concat('logo').join('.'));
+       if (logo) {
+               utils.assignPathValue(yang, putLeafPath.concat(['logo']).join('.'), logo);
+       }
+       var data = module.list;
+
+       return {name: name, data: resolveReferences(yang, data)};
+
+}
+
+function reduceModule(result, module) {
+       result[module.name] = buildProperties(module.data, 'list')[0];
+       return result;
+}
+
+var result = yang.map(module).reduce(reduceModule, {});
+
+console.log(JSON.stringify(result, null, 5));