savvy-js
Version:
Savvy - Style sheet documentation tool
492 lines (433 loc) • 16.7 kB
JavaScript
/**
* 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
};
}());