UNPKG

savvy-js

Version:

Savvy - Style sheet documentation tool

492 lines (433 loc) 16.7 kB
/** * The assembler compiles the individual parsed comments into a tree * * @class assembler * @namespace savvy */ (function () { 'use strict'; var tree = {}; function getTree() { return tree; } function extend(target, source){ for(var property in source){ if(source.hasOwnProperty(property)) { if(source[property].name) { target[property] = source[property].name; } else{ target[property] = source[property] } } } } /** * Get an exciting module or create it if it does not exist. * * @method getModule * @param {string} moduleName - the name of the module to find or create. * @returns {Object} a reference for the module in the tree. */ function getModule(moduleName) { var result = null; if (moduleName) { if (!tree[moduleName]) { tree[moduleName] = { _submodules: {}, _classes: {} }; } result = tree[moduleName]; } return result; } /** * Get an exciting submodule or create it if it does not exist. * * @method getSubmodule * @param {string} moduleName - the name of the module to find or create. * @param {string} submoduleName - the name of the submodule to find or create. * @returns {Object} a reference for the submodule in the tree. */ function getSubmodule(submoduleName, moduleName) { var result = null, module; if (submoduleName) { module = getModule(moduleName); if (module && moduleName) { if (!module._submodules[submoduleName]) { module._submodules[submoduleName] = { _classes: {} }; } result = module._submodules[submoduleName]; } else { for (var moduleName in tree) { if (tree.hasOwnProperty(moduleName) && tree[moduleName]._submodules.hasOwnProperty(submoduleName)) { result = tree[moduleName]._submodules[submoduleName]; } } } } return result; } /** * Get an exciting class or create it if it does not exist. * * @method getClass * @param {string} className - the name of the class to find or create. * @param {string} moduleName - the name of the module to find or create. * @param {string} submoduleName - the name of the submodule to find or create. * @returns {Object} a reference for the class in the tree. */ function getClass(className, moduleName, submoduleName) { var result = null, module, submodule; if (submoduleName) { submodule = getSubmodule(submoduleName, moduleName); if (submodule) { if (!submodule._classes[className]) { submodule._classes[className] = { class: className }; } result = submodule._classes[className]; } } else if (moduleName) { module = getModule(moduleName); if (!module._classes[className]) { module._classes[className] = { class: className }; } result = module._classes[className]; } else { result = findClassRecursive(tree, className); // class name was not found anywhere and no module or submodule provided, created it under tree classes. if(!result){ if(!tree._classes){ tree._classes = {}; } tree._classes[className] = { class: className }; result = tree._classes[className]; } } return result; } function findClassRecursive(node, className){ var result, i; if(node._classes){ result = findClassRecursive(node._classes, className); } else if(node._submodules){ result = findClassRecursive(node._submodules, className); } else if(node.states && node.states.length){ i = node.states.length; while(i--){ result = findClassRecursive(node.states[i], className); if(result){ break; } } } else if(node.children && node.children.length){ i = node.children.length; while(i--){ result = findClassRecursive(node.children[i], className); if(result){ break; } } } if(!result) { if (node.class) { if (node.class === className) { result = node; } } else { for (var propertyName in node) { if (node.hasOwnProperty(propertyName) && propertyName !== '_classes') { result = findClassRecursive(node[propertyName], className); } } } } return result; } function getName(statement){ var result; if(statement && statement.name){ result = statement.name; } return result; } function handleParent(parsedComment) { var classObject = getClass(getName(parsedComment.parent), getName(parsedComment.module), getName(parsedComment.submodule)), revisedObject = {}; if(!classObject.children){ classObject.children = []; } delete parsedComment.parent; extend(revisedObject, parsedComment); classObject.children.push(revisedObject); } function handleStateOf(parsedComment) { var classObject = getClass(getName(parsedComment.stateof),getName(parsedComment.module), getName(parsedComment.submodule)), revisedObject = {}; if(!classObject.states){ classObject.states = []; } delete parsedComment.stateof; extend(revisedObject, parsedComment); classObject.states.push(revisedObject); } function handleClass(parsedComment) { var classObject = getClass(getName(parsedComment.class), getName(parsedComment.module), getName(parsedComment.submodule)); if(classObject) { extend(classObject, parsedComment); } } function handleSubmodule(parsedComment) { var submodule = getSubmodule(getName(parsedComment.submodule), getName(parsedComment.module)); if(submodule) { extend(submodule, parsedComment); } } function handleModule(parsedComment) { var module = getSubmodule(getName(parsedComment.module)); if(module) { extend(module, parsedComment); } } /** * Sort comments by relevance, so it would prevent errors in the tree parsing phase * * @method sortComments * @param {Array} parsedComments */ function sortComments(parsedComments){ function getWheight(comment){ var weight; if (comment.module && comment.submodule) { weight = 10000; } else if (comment.module) { weight = 1000; } else if (comment.submodule) { weight = 100; } else { weight = 10; } return weight; } parsedComments.sort(function(commentA, commentB){ var weightA = getWheight(commentA), weightB = getWheight(commentB); return weightA > weightB ? -1 : (weightA < weightB ? 1 : 0); }); } /** * Convert a flat list of comments into a hierarchical tree. Return this tree format: * * { * // Collection of root level classes that has no module or submodule * _classes : { * sampleClass : { * name : sampleClass, // The name of the class * line : 1, // Line number in file in which it appears * file : 'style/myFile.css' // File name in which it appears * states : [ * stateClass { ... }, // Another class that represents a different states of the sampleClass * ... * ], * children : [ * childClass { ... }, // Other classes the compose parts of the larger component sampleClass. For example, a child might be a close button inside a dialog. * ... * ] * ), * ... * }, * sampleModule { * // Collection of module level classes that has no submodule * _classes : { * sampleClass : { ... }, * ... * }, * // Collecttion of submodules for this module * _submodules : { * sampleSubmodule : { * // Collection of classes for this submodule * _classes : { * sampleClass : { ... }, * ... * } * }, * anotherSubmodule { ... }, * } * }, * anotherModule : {...}, * ... * } * * As you can see, each level has it's own '_classes' attribute so that you could generate a flat classes tree, one * level tree or two level tree just by using the '@module' or '@submodule' statements in your comments. * * @method assemble * @param {Array} parsedComments * @returns {Object} all parsed comments as one big tree. */ function assemble(parsedComments) { var classObject, treeClasses; tree = {}; function mergeDetachedClass(attachedClass, detachedClass){ if(detachedClass.states && detachedClass.states.length > 0){ if(!attachedClass.states){ attachedClass.states = []; } attachedClass.states = [].concat(attachedClass.states, detachedClass.states); } if(detachedClass.children && detachedClass.children.length > 0){ if(!attachedClass.children){ attachedClass.children = []; } attachedClass.children =[].concat( attachedClass.children, detachedClass.children); } } sortComments(parsedComments); parsedComments.forEach(function (parsedComment) { if (parsedComment.parent) { handleParent(parsedComment); } else if (parsedComment.stateof) { handleStateOf(parsedComment); } else if (parsedComment.class) { handleClass(parsedComment); } else if (parsedComment.submodule) { handleSubmodule(parsedComment); } else if (parsedComment.module) { handleModule(parsedComment); } }); // now that the tree is built, we will go over all the 'orphan' classes under the root and try to rematch them treeClasses = tree._classes; if(treeClasses){ // temporarily remove the tree classes so we won't iterate over them delete tree._classes; for(var className in treeClasses){ if(treeClasses.hasOwnProperty(className)){ classObject = findClassRecursive(tree, className); if(classObject) { mergeDetachedClass(classObject, treeClasses[className]); delete treeClasses[className]; } } } tree._classes = treeClasses; } return tree; } // Convert tree collections to arrays, a more convenient way to render. function treeToArray(tree){ var treeClasses, treeModules; function sortBy(array, propertyName){ array.sort(function(itemA, itemB){ return itemA[propertyName] > itemB[propertyName] ? 1 : ( itemA[propertyName] < itemB[propertyName] ? -1 : 0); }); } function convertClassCollectionToList(classCollection, moduleName, submoduleName){ var list = [], classObject; for(var className in classCollection){ if(classCollection.hasOwnProperty(className)) { classObject = classCollection[className]; if(submoduleName){ classObject.submodule = submoduleName; } if(moduleName){ classObject.module = moduleName; } if(classObject.states){ sortBy(classObject.states, 'class'); } if(classObject.children){ sortBy(classObject.children, 'class'); } list.push(classObject); } } sortBy(list, 'class'); return list; } function converSubmoduleCollectionToList(submoduleCollection, moduleName){ var list = [], submodule; for(var submoduleName in submoduleCollection){ if(submoduleCollection.hasOwnProperty(submoduleName) && submoduleName !== '_.classes'){ submodule = submoduleCollection[submoduleName]; submodule.name = submoduleName; if(moduleName){ submodule.module = moduleName; } submodule.classes = convertClassCollectionToList(submodule._classes, moduleName, submoduleName); delete submodule._classes; list.push(submodule); } } sortBy(list, 'name'); return list; } function converModuleCollectionToList(moduleCollection){ var list = [], module; for(var moduleName in moduleCollection){ if(moduleCollection.hasOwnProperty(moduleName) && moduleName !== '_.classes'){ module = moduleCollection[moduleName]; module.name = moduleName; module.classes = convertClassCollectionToList(module._classes, moduleName); module.submodules = converSubmoduleCollectionToList(module._submodules, moduleName); delete module._classes; delete module._submodules; list.push(module); } } sortBy(list, 'name'); return list; } treeClasses = convertClassCollectionToList(tree._classes); delete tree._classes; treeModules = converModuleCollectionToList(tree); tree = { classes : treeClasses, modules : treeModules }; return tree; } module.exports = { assemble: assemble, getClass: getClass, getModule: getModule, getSubmodule: getSubmodule, getTree: getTree, sort : sortComments, extend : extend, findClassRecursive : findClassRecursive, treeToArray : treeToArray }; }());