ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
363 lines (362 loc) • 11.3 kB
JavaScript
enyo.kind({
name: "analyzer.Indexer",
kind: null,
group: "public",
constructor: function() {
this.objects = [];
this.design = {};
},
debug: false,
findByName: function(inName) {
return analyzer.Documentor.findByProperty(this.objects, "name", inName);
},
findByTopic: function(inTopic) {
return analyzer.Documentor.findByProperty(this.objects, "topic", inTopic);
},
/**
Creates a new array with all elements of _inArray_ that pass the test implemented by _inFunc_.
If _inContext_ is specified, _inFunc_ is called with _inContext_ as _this_.
*/
search: function(inFilterFn, inMapFn, inContext) {
var values = enyo.filter(this.objects, inFilterFn, inContext);
return enyo.map(values, inMapFn, inContext);
},
// Normalizes _inPath_ by removing any _._ or _.._'s from the path
normalizePath: function(inPath) {
var parts = inPath.split("/");
var newpath = [];
enyo.forEach(parts, function(part) {
if (part == ".") {
// Do nothing
} else if (part == "..") {
newpath.pop();
} else {
newpath.push(part);
}
}, this);
return newpath.join("/");
},
addModules: function(inModules) {
enyo.forEach(inModules, this.addModule, this);
// sort (?!)
this.objects.sort(analyzer.Indexer.nameCompare);
},
addModule: function(inModule) {
//
// "indexing" refers to normalizing object records and resolving references.
// "merging" refers to adding the normalized records to the master database.
//
// indexing and merging have to be separated so we can index 'in-progress' modules
// without adding them to the database
if (this.debug) {
enyo.log("analyzer.Indexer.addModule(): + " + inModule.path);
}
inModule.path = this.normalizePath(inModule.path);
this.indexModule(inModule);
this.mergeModule(inModule);
},
mergeModule: function(inModule) {
// add this module to the database
this.objects.push(inModule);
// add the module objects to the database
this.objects = this.objects.concat(inModule.objects);
// add the module objects' properties to the database
enyo.forEach(inModule.objects, this.mergeProperties, this);
},
mergeProperties: function(inObject) {
if (inObject.properties) {
this.objects = this.objects.concat(inObject.properties);
}
// globals
else if (inObject.value && inObject.value[0] && inObject.value[0].properties /*&& inObject.value[0].properties[0] != undefined*/) {
this.objects = this.objects.concat(inObject.value[0].properties);
}
},
indexModule: function(inModule) {
// this object is type: "module"
inModule.type = "module";
inModule.module = inModule;
// name this module by incorporating the path so its unique
inModule.name = inModule.path? inModule.path.replace("lib/", ""): inModule.label + "/" + inModule.rawPath;
// parse module objects
inModule.objects = new analyzer.Documentor(new analyzer.Parser(new analyzer.Lexer(inModule.code)));
// index module objects
this.indexObjects(inModule);
},
/**
* Removes all indexer data associated with the specified javascript module
* @param inModule An object containing at least a path to the file
* @public
*/
removeModule: function(inModule) {
this.removeModuleByPath(inModule.path);
},
/**
* Removes all indexer data associated with the specified javascript module
* @param inPath The path to the file
* @public
*/
removeModuleByPath: function(inPath) {
inPath = this.normalizePath(inPath);
// Remove all objects associated with this module
var len = this.objects.length;
while (len--) {
if (this.objects[len].module.path == inPath) {
this.objects.splice(len, 1);
}
}
},
/**
* Removes all indexer data associated with the specified javascript module, and
* re-indexes it.
* @param inModule An object containing the path and code of the file
* @public
*/
reIndexModule: function(inModule) {
this.removeModule(inModule);
this.addModule(inModule);
},
indexObjects: function(inModule) {
enyo.forEach(inModule.objects, function(o) {
o.module = inModule;
this.indexObject(o);
}, this);
},
indexObject: function(inObject) {
switch (inObject.type) {
case "kind":
this.indexKind(inObject);
break;
}
this.indexProperties(inObject);
},
indexProperties: function(inObject) {
var p$ = inObject.properties || (inObject.value && inObject.value[0] && inObject.value[0].properties);
enyo.forEach(p$, function(p) {
p.object = inObject;
p.topic = p.object.name ? p.object.name + "::" + p.name : p.name;
p.module = inObject.module;
/*
if (p.value && p.value[0] && p.value[0].properties) {
this.indexProperties(p.value[0].properties);
}
*/
}, this);
},
indexKind: function(o) {
// build a flat list of component declarations
this.listComponents(o);
// discover superkinds and inherited properties
this.indexInheritance(o);
/*
// append published properties to main property list
var i = analyzer.Documentor.indexByName(o.properties, "published");
if (i >= 0) {
var pp = o.properties[i];
o.properties.splice(i, 1);
pp = pp.value && pp.value[0] && pp.value[0].properties;
for (var j=0, p; p = pp[j]; j++) {
p.published = true;
p.group = "published";
o.properties.splice(i, 0, p);
}
}
*/
},
indexInheritance: function(o) {
o.superkinds = this.listSuperkinds(o);
o.allProperties = this.listInheritedProperties(o);
},
listSuperkinds: function(o) {
var supers = [], sk;
while (o && o.superkind) {
sk = o.superkind;
o = this.findByName(sk);
if (!o) {
o = this.findByName("enyo." + sk);
if (o) {
sk = "enyo." + sk;
}
}
supers.push(sk);
}
return supers;
},
listInheritedProperties: function(o) {
var all = [], map = {};
// walk up the inheritance chain from the basest base
for (var i=o.superkinds.length - 1, n; (n=o.superkinds[i]); i--) {
// find the superkind properties
var sk = this.findByName(n);
if (sk) {
// merge the superkind properties
this.mergeInheritedProperties(sk.properties, map, all);
}
}
// merge the kind's own properties
this.mergeInheritedProperties(o.properties, map, all);
// default sort
all.sort(analyzer.Indexer.nameCompare);
// return the list
return all;
},
mergeInheritedProperties: function(inProperties, inMap, inAll) {
if (inProperties) {
for (var j=0, p; (p=inProperties[j]); j++) {
// look for overridden property
var old = inMap.hasOwnProperty(p.name) && inMap[p.name];
if (old) {
// note the override, reference the previous instance
p.overrides = old;
// update array (only store latest property)
inAll[enyo.indexOf(old, inAll)] = p;
} else {
// new property
inAll.push(p);
}
// update temporary property map
inMap[p.name] = p;
}
}
},
listComponents: function(o) {
// produce a list of components owned by 'o' as specified by 'components' property
o.components = this._listComponents(o, [], {});
// add componentsBlockStart and componentsBlockEnd properties for Ares
var c$ = analyzer.Documentor.findByName(o.properties, "components");
if (c$ && c$.value) {
o.componentsBlockStart = c$.value[0].start;
o.componentsBlockEnd = c$.value[0].end;
}
},
_listComponents: function(o, list, map) {
// if 'components' exists, it's a property with a block value
var c$ = analyzer.Documentor.findByName(o.properties, "components");
if (c$ && c$.value && c$.value.length) {
// the array of properties in the block value
var p$ = c$.value[0].properties;
for (var i=0, p; (p=p$[i]); i++) {
if (p.type !== 'block') {
var errMsg = "Error in kind "+ o.name + ": Expected an object in component list not " + p.type + ' ' + p.token ;
enyo.log(errMsg);
throw new Error(errMsg);
}
// each p is a config block, find the 'name' and 'kind' properties, if they exist
var n = analyzer.Documentor.findByName(p.properties, "name");
if (n) {
n = analyzer.Documentor.stripQuotes(n.value[0].token || "");
}
var k = analyzer.Documentor.findByName(p.properties, "kind");
// FIXME: default kind is 'Control' only if the DOM package is loaded
k = analyzer.Documentor.stripQuotes(k && k.value[0].token || "Control");
// in Component, anonymous sub-components are named by enumerating kinds, recreate that here
if (!n) {
// only grab the last bit of the namespace
var ns = k.split(".").pop();
// uncap the first letter
n = enyo.uncap(ns);
// enumerate multiple instances of one kind (kind, kind2, kind3 ...)
if (map[n]) {
n += ++map[n];
} else {
map[n] = 1;
}
}
// make a note of the kind and processed name
p.kind = k;
p.name = n;
// add this entry in our list
list.push(p);
// sub-component definitions are owned by the top-level object
this._listComponents(p, list, map);
}
}
return list;
},
/**
* Adds data from an array of "design" objects to the indexer, which were previously
* loaded by the Reader into each design object's `code` property.
* @param inDesigns Array of design objects with unparsed code string
* @public
*/
addDesigns: function(inDesigns) {
enyo.forEach(inDesigns, this.addDesign, this);
},
/**
* Adds a given "design" object to the indexer.
* @param inDesigns A design object with unparsed code string
* @public
*/
addDesign: function(inDesign) {
inDesign.path = this.normalizePath(inDesign.path);
try {
var design = enyo.json.parse(inDesign.code);
var keys = enyo.keys(design);
enyo.forEach(keys, function(type) {
if (design[type]) {
var src = design[type];
var dest = this.design[type] || [];
enyo.forEach(src, function(item) {
item.design = inDesign;
}, this);
this.design[type] = dest.concat(src);
}
}, this);
} catch (err) {
enyo.warn("Error parsing designer meta-data (" + inDesign.path + "): " + err);
}
},
/**
* Removes all indexer data associated with the specified design file
* @param inDesign An object containing at least a path to the design file
* @public
*/
removeDesign: function(inDesign) {
try {
var keys = enyo.keys(this.design);
enyo.forEach(keys, function(category) {
this.removeDesignByPath(inDesign.path, category);
}, this);
} catch (err) {
enyo.warn("Error removing designer (" + inDesign + "): " + err);
}
},
/**
* Removes all indexer data associated with the specified design file
* @param inPath The path to the design file
* @public
*/
removeDesignByPath: function(inPath, category) {
inPath = this.normalizePath(inPath);
var len = this.design[category].length;
while (len--) {
var cat = this.design[category][len];
if (cat.design.path == inPath) {
this.design[category].splice(len, 1);
}
}
},
/**
* Removes all indexer data associated with the specified design file, and
* re-indexes it.
* @param inDesign An object containing the path and code of the design file
* @protected
*/
reIndexDesign: function(inDesign) {
this.removeDesign(inDesign);
this.addDesign(inDesign);
},
statics: {
nameCompare: function(inA, inB) {
var na = inA.name.toLowerCase(),
nb = inB.name.toLowerCase();
if (na < nb) {
return -1;
}
if (na > nb) {
return 1;
}
return 0;
}
}
});