#doc3 .classopts { display:none; }
/*
- * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
*/
+
+
+/*jslint
+ anon:true, sloppy:true, regexp: true, continue: true, nomen:true, node:true
+*/
+
+
+var libfs = require('fs'),
+ libpath = require('path'),
+ libqs = require('querystring'),
+ libvm = require('vm'),
+ libglob = require('./glob'),
+ libycb = require('./libs/ycb'),
+
+ isPositiveInt = /^[0-9]+$/,
+ isSpaceOpencurly = / \{/g,
+ isSpaceClosecurly = / \}/g,
+ isNotAlphaNum = /[^a-zA-Z0-9]/,
+
+ // fallback logger
+ // This is the logger that is used until setLogger() is called.
+ logger = {
+ log: function(msg, lvl, who) {
+ var log = console.log;
+
+ if (lvl === 'warn' || lvl === 'error') {
+ // console.warn provides unbuffered output and avoids message
+ // truncation when process.exit() is called after logging
+ log = console.warn;
+ }
+ log('[' + lvl + '] ' + who + ': ' + msg);
+ }
+ },
+
+ // nodejs-yui3 has global state about which modules are loaded. Use
+ // multiple require()'d instances as a wall to prevent cross-contamination
+ // when using loader for dependency calculations.
+ utilYUI = require('yui3').YUI,
+ serverYUI = require('yui3').YUI,
+ clientYUI = require('yui3').YUI,
+
+ Y = utilYUI().useSync('intl'),
+
+ mojitoRoot = __dirname,
+
+ NAME = 'MojitoServer';
+
+// TODO: move string constants here.
+
+
+// The Affinity object is to manage the use of the affinity string in
+// filenames. Some files have affinities that have multiple parts
+// (e.g. "server-tests").
+function Affinity(affinity) {
+ this._init(affinity);
+}
+
+
+Affinity.prototype = {
+ _init: function(aff) {
+ var parts;
+
+ if (aff.indexOf('-') === -1) {
+ this.affinity = aff;
+ } else {
+ parts = aff.split('-');
+ this.affinity = parts[0];
+ this.type = parts[1];
+ }
+ },
+ toString: function() {
+ return this.affinity;
+ }
+};
+
+
/**
- * A "resource" is the smallest-sized component in the mojito framework.
+ * The Resource Store manages information about the "resources" in a Mojito
+ * application. These resources are things that have representation on the
+ * filesystem.
*
- * It has a representation on disk.
- * Sometimes a resource will be fully represented by a single file.
- * Sometimes multiple files together will comprise one resource.
- * Sometimes multiple resources will be defined in one file.
- * It is the job of the resource store to manage all this.
+ * Each resource can have many different versions. This is not talking about
+ * revisions, which is how the resource changes over time. It is instead
+ * talking about how there can be a version of the resource just for iphones,
+ * one just for android, a fallback, etc.
*
- *
- * RESOURCE TYPES
- * config -- a piece of configuration, sometimes for another resource
+ * There are various types of resources:
+ * <pre>
+ * config -- a piece of configuration, sometimes for another resource
* controller -- the controller for a mojit
* model -- a model for a mojit
* view -- a view for a mojit
@@ -69,18 +148,20 @@ MojitoServer 0.1.0
* action -- an action to augment the controller
* asset -- an asset (css, js, image, etc)
* addon -- an addon to the mojito system
+ * spec -- the configuration for a mojit instance
* yui-lang -- a YUI3 language bundle
* yui-module -- a YUI3 module (that isn't one of the above)
+ * </pre>
*
- *
- * RESOURCE METADATA
- * (not all resources will have all details)
+ * The metadata kept about each resource is "normalized" to the follow keys:
+ * (not all resources will have all keys)
+ * (some types will have additional keys)
* (not all combinations of type:source are valid)
*
+ * <pre>
* - id
* context-insensitive ID of the resource
- * (type + 'app' + (details specific to type))
- * (type + 'mojit' + mojit-type + (details specific to type))
+ * said another way, all versions of a resource have the same ID
*
* - type
* see above
@@ -93,8 +174,8 @@ MojitoServer 0.1.0
* the path on the filesystem
*
* - staticHandlerURL
- * for resources that can be deployed by reference to the client
* the URL that will cause the asset handler to serve the resource
+ * for resources that can be deployed by reference to the client
*
* - name
* specific to type
@@ -138,134 +219,87 @@ MojitoServer 0.1.0
* a list of YUI modules required by the module,
* with transitive dependencies resolved
* format: { yui-module-name: URL-to-load-yui-module }
- *
+ * </pre>
*
- *
- * @class ServerStore
+ * @module MojitoServer
+ * @class ResourceStore.server
* @constructor
- * @param root
- * @private
- */
-
-var libfs = require('fs'),
- libpath = require('path'),
- libqs = require('querystring'),
- libvm = require('vm'),
- libglob = require('./glob'),
- libycb = require('./libs/ycb'),
-
- isPositiveInt = /^[0-9]+$/,
- isSpaceOpencurly = / \{/g,
- isSpaceClosecurly = / \}/g,
-
-
- // fallback logger
- logger = {
- log: function(msg, lvl, who) {
- var log = console.log;
- if (lvl === "warn" || lvl === "error") {
- // console.warn provides unbuffered output and avoids message
- // truncation when process.exit() is called after logging
- log = console.warn;
- }
- log('['+lvl+'] '+who+': '+msg);
- }
- },
-
- YUI = require('yui3').YUI,
-
- // re-requiring seems to be necessary if useSync() will be called on both.
- // if removed and YUI used instead an error is thrown on page load
- YUI2 = require('yui3').YUI,
-
- mojitoRoot = __dirname,
-
- NAME = 'MojitoServer';
-
-// TODO 2011-06-16: move string constants here
-
-
-function Affinity(affinity) {
- this._init(affinity);
-}
-Affinity.prototype = {
- _init: function(aff) {
- var parts;
- if (aff.indexOf('-') === -1) {
- this.affinity = aff;
- } else {
- parts = aff.split('-');
- this.affinity = parts[0];
- this.type = parts[1];
- }
- },
- toString: function() {
- return this.affinity;
- }
-};
-
-/*
- * * root string
- * directory of the application
- * * libs object
- * set of libraries to use
- * - ycb: YCB library to use, if available
- * - fs: filesystem library to use during unittesting
- *
+ * @param root {string} directory where application is found
+ * @param libs {object} dependent libraries -- this param is mainly used
+ * during unit testing
*/
function ServerStore(root, libs) {
//logger.log('ctor(' + root + ')');
this._root = root;
- this._version = '0.1.0';
+ this._version = '0.3.0';
this._shortRoot = libpath.basename(root);
this._libs = libs || {};
- // no-context version of the appConfig
- this._appConfigNC = null;
-
- // each of these is [resid][affinity].contexts
- // [resid][affinity][ctx] = { parts }
- // mojits have [type] prefixed
+ // the static version of the appConfig (application.json)
+ // It's "static" because it's determined at server-start time, and doesn't
+ // change after that.
+ this._appConfigStatic = null;
+
+ // Each of these is a complex datastructure, the first key of which is the
+ // resource ID ("resid"). (For mojitMeta, the first key is the mojit type
+ // and the second key is resid.)
+ // Under resid the next key is the affinity ("client", "server", or
+ // "common".)
+ // Under affinity is a datastructure that tracks all versions of the resource.
+ // There is a special key "contexts" which lists all the contexts that the
+ // resource has been specialized for. "contexts" is an object. The key is a
+ // string that identifies the context, and the value is a partial context that
+ // describes the specialization. (An example might be "device=iphone" for
+ // the key and { device:'iphone' } for the value.)
+ // The rest of the keys are the context strings (as found in "contexts"), and
+ // the values are the metadata about the resource versions.
+ // [resid][affinity].contexts
+ // [resid][affinity][ctxKey] = { metadata }
+ // (These are mostly populated by the _preloadSetDest() method.)
this._preload = {
fwMeta: {},
appMeta: {},
mojitMeta: {}
};
+
+ // These are similar to the _preload above, except the affinity has been resolved
+ // down for each environment ("client" or "server"). Also, the ctxKey has been
+ // moved above resid to optimize lookup during runtime.
+ this._fwMeta = {}; // [env][ctxKey][resid] = { parts }
+ this._appMeta = {}; // [env][ctxKey][resid] = { parts }
+ this._mojitMeta = {}; // [env][type][ctxKey][resid] = { parts }
+ this._mojitYuiRequired = {}; // [env][type][ctxKey] = [ YUI module names ]
+ this._mojitYuiSorted = {}; // [env][type][ctxKey] = [ YUI module names ]
+ this._mojitYuiSortedPaths = {}; // [env][type][ctxKey][module] = path
+
+ this._jsonCache = {}; // fullpath: contents as JSON object
+ this._ycbCache = {}; // fullpath: context: YCB config object
+
this._staticURLs = {}; // url => fullpath
this._dynamicURLs = {}; // url => dynamic content
this._mojitAssetRoots = {}; // mojitType => URL prefix
this._mojitLangs = {}; // mojitType => [en-US,en-GB,en]
this._mojitPaths = {}; // mojitType => filesystem directory of mojit
- this._fwMeta = {}; // [env][ctx][resid] = { parts }
- this._appMeta = {}; // [env][ctx][resid] = { parts }
- this._mojitMeta = {}; // [env][type][ctx][resid] = { parts }
- this._mojitYuiRequired = {}; // [env][type][ctx] = [ list of YUI module names ]
- this._mojitYuiSorted = {}; // [env][type][ctx] = [ list of YUI module names ]
- this._mojitYuiSortedPaths = {}; // [env][type][ctx][module] = path
- this._configCache = {}; // fullpath: content
- this._ycbCache = {}; // fullpath: ycb config object
-
this._expandInstanceCache = { // [env][cacheKey] = instance
client: {},
server: {}
};
- this._Y = YUI2().useSync('intl');
-
- // TODO 2011-06-16: bake this into the refactor
+ // TODO: bake this into the refactoring work.
// this stuff is mainly so that we can send mocks during testing
- if (! this._libs.fs) {
+ if (!this._libs.fs) {
this._libs.fs = libfs;
}
- if (! this._libs.path) {
+ if (!this._libs.path) {
this._libs.path = libpath;
}
- if (libycb && (! this._libs.ycb)) {
+ if (libycb && (!this._libs.ycb)) {
this._libs.ycb = libycb;
}
}
+
ServerStore.prototype = {
// ===========================================
@@ -275,49 +309,56 @@ MojitoServer 0.1.0
/**
* Preloads everything in the app, and as well pertinent parts of
* the framework.
- * @method preload
- * @applicationEnvironment ??
- * @appConfig overrides for the app config
- * @return {undefined}
+ *
+ * @param {object} appContext the base context for reading configuration.
+ * @param {object} appConfig overrides for the app config.
+ * @return {nothing}
*/
- preload: function(applicationEnvironment, appConfig) {
+ preload: function(appContext, appConfig) {
//logger.log('preload()');
- var type, ctxKey, resid, res, n;
+ var type,
+ ctxKey,
+ resid,
+ res,
+ n;
- if (! this._preload) {
+ if (!this._preload) {
// we've already been preloaded
// This situation mainly happens in the commandline scripts.
return;
}
- this._fwConfig = this._readConfigJSON(libpath.join(mojitoRoot, 'config.json'));
+ this._fwConfig = this._readConfigJSON(libpath.join(mojitoRoot,
+ 'config.json'));
+ this._ycbDims = this._readYcbDimensions();
+ this._validYCBDims = this._precalcValidYCBDimensions(
+ this._ycbDims[0].dimensions
+ );
+ this._defaultYCBContext = appContext || {};
+
+ // need to read the statically configured appConfig now so that values
+ // are available during generation of the static URLs
+ this._appConfigStatic = this._readAppConfigStatic();
- // TODO: use applicationEnvironment while reading, so that env=x
- // contextualization in the application.json takes effect
- // need to read the non-context appConfig now so that values are available during
- // generation of the static URLs
- this._appConfigNC = this._readAppConfigNC();
-
// generates URL's about each spec in application.json
this._urlsForAppSpecs();
- // TODO: [bug 4647260] allow the environment to be overridden within the app config
- this._appConfigNC.env = applicationEnvironment || 'dev';
-
// merge app configuration overrides
if (appConfig) {
for (n in appConfig) {
if (appConfig.hasOwnProperty(n)) {
- logger.log('overriding application config value: ' + n, 'warn');
- this._appConfigNC[n] = appConfig[n];
+ logger.log('overriding application config value: ' + n,
+ 'warn');
+ this._appConfigStatic[n] = appConfig[n];
}
}
}
- // generates metadata about each file
+ // generates metadata about each resource
this._preloadMeta();
-
- // takes the preloaded info and resolves affinity, etc
+
+ // takes the preloaded info about each resource and resolves
+ // version priority (.server. more specific that .common. etc)
this._cookdown();
// preread configs
@@ -328,11 +369,12 @@ MojitoServer 0.1.0
}
}
- if ('precomputed' === this._appConfigNC.yui.dependencyCalculations) {
- this._precalcYuiDependencies();
- }
+ // We need to run this function even for "ondemand", since it calculates
+ // additional implied dependencies, such as a binder on MojitoClient,
+ // or a controller on the view-engine needed to render its views.
+ this._precalcYuiDependencies();
- // binders are client-side-only constructs, yet we need to know about
+ // binders are client-side-only resources, yet we need to know about
// them when talking about the 'server' environment
for (type in this._mojitMeta.client) {
if (this._mojitMeta.client.hasOwnProperty(type)) {
@@ -341,12 +383,15 @@ MojitoServer 0.1.0
if (this._mojitMeta.client[type].hasOwnProperty(ctxKey)) {
for (resid in this._mojitMeta.client[type][ctxKey]) {
- if (this._mojitMeta.client[type][ctxKey].hasOwnProperty(resid)) {
- res = this._mojitMeta.client[type][ctxKey][resid];
+ if (this._mojitMeta.client[type][ctxKey].
+ hasOwnProperty(resid)) {
+ res = this._mojitMeta.client[type
+ ][ctxKey][resid];
if (res.type !== 'binder') {
continue;
}
- this._mojitMeta.server[type][ctxKey][resid] = res;
+ this._mojitMeta.server[type
+ ][ctxKey][resid] = res;
}
}
}
@@ -355,22 +400,36 @@ MojitoServer 0.1.0
}
},
+
+ /**
+ * Sets the logger object.
+ *
+ * @param l {object} object containing a log(message,level,source) function
+ * @return {nothing}
+ */
setLogger: function(l) {
logger = l;
},
- /*
- * TODO: RIC [bug 4647265]
+
+ /**
+ * Returns, via callback, the fully expanded mojit instance specification.
+ *
+ * @param env {string} either "client" or "server"
+ * @param id {string} the ID of the spec to return
+ * @param context {object} the runtime context for the spec
+ * @param callback {function(err,spec)} callback used to return the results (or error)
+ * @return {nothing} results returned via the callback parameter
*/
- getSpec: function(env, id, context, callback){
+ getSpec: function(env, id, context, callback) {
- this.expandInstanceForEnv(env, {base: id}, context, function(err, obj){
- if(err){
+ this.expandInstanceForEnv(env, {base: id}, context, function(err, obj) {
+ if (err) {
callback(err);
return;
}
- if(env === 'client' && obj){
+ if (env === 'client' && obj) {
delete obj.assets;
}
@@ -378,18 +437,26 @@ MojitoServer 0.1.0
});
},
- /*
- * TODO: RIC [bug 4647330]
+
+ /**
+ * Returns, via callback, the details of the mojit type.
+ *
+ * @param env {string} either "client" or "server"
+ * @param type {string} the mojit type
+ * @param context {object} the runtime context for the spec
+ * @param callback {function(err,spec)} callback used to return the results (or error)
+ * @return {nothing} results returned via the callback parameter
*/
- getType: function(env, type, context, callback){
+ getType: function(env, type, context, callback) {
- this.expandInstanceForEnv(env, {type: type}, context, function(err, obj){
- if(err){
+ this.expandInstanceForEnv(env, {type: type}, context, function(err,
+ obj) {
+ if (err) {
callback(err);
return;
}
- if(env === 'client' && obj){
+ if (env === 'client' && obj) {
delete obj.assets;
}
@@ -400,77 +467,90 @@ MojitoServer 0.1.0
/**
* This just calls expandInstanceForEnv() with `env` set to `server`.
*
- * @method expandInstance
- * @param instance {map} partial instance to expand
- * @param ctx {object} the request context
- * @param cb {function(err,instance)} the callback to send the results to
- * @return nothing. results passed back via the callback
+ * @param instance {map} Partial instance to expand.
+ * @param ctx {object} The request context.
+ * @param cb {function(err,instance)} callback used to return the results (or error)
+ * @return {nothing} results returned via the callback parameter
*/
expandInstance: function(instance, ctx, cb) {
- return this.expandInstanceForEnv('server', instance, ctx, cb);
+ this.expandInstanceForEnv('server', instance, ctx, cb);
+ return;
},
+
/**
- * This method takes a partial instance and expands it to all details needed to run the mojit.
+ * This method takes a partial instance and expands it to all details needed
+ * to run the mojit.
*
- * Only a `base` or `type` fields are required. You should only specify one.
+ * Only `base` or `type` fields are required. You should only specify one.
*
* <pre>
* instance: {
- * base: "",
+ * base: string
* // specifies a "base" instance which this instance will extend
- * // the value refers to a key of `specs` found in `application.json`
- * type: "",
+ * // the value refers to a key of `specs` in `application.json`
+ * type: string
* // specifies the mojit type
* action: "",
- * // specifies a default action if the instance isn't dispatched with a specific one
- * config: {...},
+ * // specifies a default action if the instance isn't dispatched
+ * // with a specific one.
+ * config: object
* // the config for the mojit
- * // this will be augmented (appropriately) with the mojit type defaults
- * // found in the type's `defaults.json`
- * appConfig: {...},
+ * // this will be augmented (appropriately) with the mojit type
+ * // defaults found in the type's `defaults.json`
+ * appConfig: object
* // the application config (appropriate for the context)
* assetRoot: "",
* // path to directory containing assets
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
- * definition: "",
+ * // the path will be a URL if `env` is `client` otherwise it's a
+ * // filesystem path
+ * definition: object
* // the body of the `defintion.json` for the mojit type
- * defaults: "",
+ * defaults: object
* // the body of the `defaults.json` for the mojit type
* yui: {
* // details for generating a YUI sandbox for this instance
* config: {
- * // configuration details for the YUI.GlobalConfig.groups (or an equivalent).
- * // the module paths are given as `fullpath` and contain either a URL if `env'
- * // is `client` or a filesystem path if `env` is `server`
+ * // configuration details for the YUI.GlobalConfig.groups (or
+ * // an equivalent).
+ * // The module paths are given as `fullpath` and contain
+ * // either a URL if `env' is `client` or a filesystem path if
+ * // `env` is `server`
* },
* requires: []
* // list of YUI modules that this instance requires
- * },
- * actions: [],
+ * }
+ * actions: array
* // list of paths to the YUI modules containing actions
- * controller: "",
+ * controller: string
* // path to controller
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
+ * // the path will be a URL if `env` is `client` otherwise it's a
+ * // filesystem path
* lang:
* // path to YUI module of the language bundle
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
- * models: {},
+ * // the path will be a URL if `env` is `client` otherwise it's a
+ * // filesystem path
+ * models: object
* // list of models used by the mojit type
- * // the key is the model name, and the value is the path to the model file
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
+ * // the key is the model name, and the value is the path to the
+ * // model file
+ * // the path will be a URL if `env` is `client` otherwise it's a
+ * // filesystem path
* views: {
* // list of views in the mojit type
- * // the key is the view name, and the value is details about the view
+ * // the key is the view name, and the value is details about the
+ * // view
* view-name: {
* "content-path": "",
* // the path to use to load the body of the view
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
+ * // the path will be a URL if `env` is `client` otherwise
+ * // it's a filesystem path
* "engine": "",
* // which engine is used to render the view
* "binder-path": "",
* // the path to the binder
- * // the path will be a URL if `env` is `client` otherwise it's a filesystem path
+ * // the path will be a URL if `env` is `client` otherwise
+ * // it's a filesystem path
* "binder-module": ""
* // the YUI module name of the binder
* }
@@ -478,18 +558,20 @@ MojitoServer 0.1.0
* }
* </pre>
*
- * @method expandInstanceForEnv
- * @param env {"client"|"server"} which environment to expand the instance for
- * @param instance {map} partial instance to expand
- * @param ctx {object} the request context
- * @param cb {function(err,instance)} the callback to send the results to
- * @return nothing. results passed back via the callback
+ * @param env {string} "client" or "server"
+ * @param instance {object} partial instance to expand
+ * @param ctx {object} the runtime context for the instance
+ * @param cb {function(err,instance)} callback used to return the results (or error)
+ * @return {nothing} results returned via the callback parameter
*/
expandInstanceForEnv: function(env, instance, ctx, cb) {
- //logger.log('expandInstanceForEnv(' + env + ',' + (instance.id||'@'+instance.type) + ')');
+ //logger.log('expandInstanceForEnv(' + env + ',' +
+ // (instance.id||'@'+instance.type) + ')');
var self = this, base,
appConfig = this.getAppConfig(ctx, 'definition'),
- cacheKey = JSON.stringify(instance) + JSON.stringify(ctx),
+ cacheKey = JSON.stringify(instance) + JSON.stringify(
+ this._getValidYCBContext(ctx)
+ ),
cacheValue = this._expandInstanceCache[env][cacheKey];
if (cacheValue) {
@@ -513,7 +595,12 @@ MojitoServer 0.1.0
out.config = out.config || {};
out.action = out.action || 'index';
- out.guid = out.guid || self._Y.guid();
+ if (!out.instanceId) {
+ out.instanceId = Y.guid();
+ //DEBUGGING: out.instanceId += '-instance-server-' + out.type;
+ }
+ // DEPRECATED, kept in case a user is using it.
+ out.guid = out.instanceId;
try {
self.getMojitTypeDetails(env, ctx, out.type, out);
@@ -522,13 +609,13 @@ MojitoServer 0.1.0
}
// apply type defaults to config
- if ((! fromBase) && out.defaults && out.defaults.config) {
+ if ((!fromBase) && out.defaults && out.defaults.config) {
spec = self._cloneObj(out.defaults.config);
self._mergeRecursive(spec, out.config);
out.config = spec;
}
- if (! out.appConfig) {
+ if (!out.appConfig) {
out.appConfig = appConfig;
delete out.appConfig.specs;
}
@@ -540,12 +627,14 @@ MojitoServer 0.1.0
if (appConfig.specs) {
base = appConfig.specs[instance.base];
}
- if (! base) {
- return cb(new Error('Unknown "base" of "' + instance.base + '"'));
+ if (!base) {
+ return cb(new Error('Unknown "base" of "' + instance.base +
+ '"'));
}
// The base will need to carry it's ID with it.
base.id = instance.base;
- this.expandInstanceForEnv(env, base, ctx, function(err, baseInstance) {
+ this.expandInstanceForEnv(env, base, ctx, function(err,
+ baseInstance) {
if (err) {
return cb(err);
}
@@ -553,8 +642,7 @@ MojitoServer 0.1.0
self._mergeRecursive(temp, instance);
gotBase(temp, true);
});
- }
- else {
+ } else {
gotBase(this._cloneObj(instance), false);
}
},
@@ -563,48 +651,57 @@ MojitoServer 0.1.0
/**
* gets application configuration
*
- * @method getAppConfig
- * @param context {object} context under which to load the config
+ * @param ctx {object} the runtime context under which to load the config
* @param name {string} type of config to read:
- * - definition: reads ./application.json
- * - package: reads ./package.json
- * - routes: reads ./routes.json (or whatever was configured in appConfig('definition').routesFiles)
+ * - definition: reads ./application.json
+ * - package: reads ./package.json
+ * - routes: reads ./routes.json (or whatever was configured in
+ * appConfig('definition').routesFiles)
* @return {object} config object
*/
getAppConfig: function(ctx, name) {
//logger.log('getAppConfig('+name+')');
- var resid, res;
- if ('definition' === name && ((!ctx) || (!ctx.length))) {
- return this._cloneObj(this._appConfigNC);
+ var resid,
+ res,
+ ycb;
+
+ if ('definition' === name && (!ctx || !Object.keys(ctx).length)) {
+ return this._cloneObj(this._appConfigStatic);
}
+
resid = 'config-' + name;
res = this._getContextualizedResource(this._appMeta.server, ctx, resid);
- if (! res) {
+ if (!res) {
return {};
}
- return this._readConfigYCB(ctx, res.fsPath);
+ ycb = this._readConfigYCB(ctx, res.fsPath, true);
+ return this._cloneObj(ycb);
},
/**
* Returns the routes configured in the application.
- * @param ctx {object} context under which to load the routes
+ *
+ * @param ctx {object} runtime context under which to load the routes
* @return {object} routes
*/
getRoutes: function(ctx) {
//logger.log('getRoutes()');
- var ress, resid, res,
- r, routes = {};
-
- // TODO: [bug 4647333] trapped this error. It only appears when there is no application.json
+ var ress,
+ resid,
+ res,
+ r,
+ routes = {};
+
+ // TODO: [Issue 100] trapped this error. It only appears when there is
+ // no application.json
try {
ress = this._getResourceListForContext(this._appMeta.server, ctx);
- }
- catch(err) {
+ } catch (err) {
//logger.log(err);
ress = {};
}
-
+
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
@@ -615,40 +712,54 @@ MojitoServer 0.1.0
this._mergeRecursive(routes, r);
}
}
- if (! Object.keys(routes).length) {
+ if (!Object.keys(routes).length) {
routes = this._cloneObj(this._fwConfig.defaultRoutes);
}
return routes;
},
- /*
- * * url string
- * asset URL
- * * returns
- * filesystemm path
+ /**
+ * Returns the filesystem location of the static URL.
+ * Returns undefined if given URL isn't part of the app.
+ *
+ * @param url {string} static URL
+ * @return {string} path on filesystem of specified URL, or undefined
*/
- // TODO DOCS [bug 4647336]
fileFromStaticHandlerURL: function(url) {
//logger.log('fileFromStaticHandlerURL('+url+')');
return this._staticURLs[url];
},
- // TODO DOCS [bug 4647349]
+ /**
+ * Returns the YUI configuration object which tells YUI about the
+ * YUI modules in all the mojits.
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context for YUI configuration
+ * @return {object} YUI configuration for all mojits
+ */
getYuiConfigAllMojits: function(env, ctx) {
- // TODO DREW -- use getMojitTypeDetails() to generate this
+ // TODO: use getMojitTypeDetails() to generate this.
//logger.log('getYuiConfigAllMojits('+env+')');
- var modules = {}, type,
- ress, resid, res, ctxKey;
+ var modules = {},
+ type,
+ ress,
+ resid,
+ res,
+ ctxKey;
for (type in this._mojitMeta[env]) {
if (this._mojitMeta[env].hasOwnProperty(type)) {
- ress = this._getResourceListForContext(this._mojitMeta[env][type], ctx);
+ ress = this._getResourceListForContext(
+ this._mojitMeta[env][type],
+ ctx
+ );
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
if ('fw' === res.source) {
@@ -658,7 +769,9 @@ MojitoServer 0.1.0
continue;
}
modules[res.yuiModuleName] = {
- fullpath: ('client' === env) ? res.staticHandlerURL : res.fsPath,
+ fullpath: ('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath,
requires: res.yuiModuleMeta.requires
};
}
@@ -670,7 +783,9 @@ MojitoServer 0.1.0
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
modules[res.yuiModuleName] = {
- fullpath: ('client' === env) ? res.staticHandlerURL : res.fsPath,
+ fullpath: ('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath,
requires: res.yuiModuleMeta.requires
};
}
@@ -682,24 +797,35 @@ MojitoServer 0.1.0
},
- // TODO DOCS [bug 4647354]
+ /**
+ * Returns the YUI configuration object which tells YUI about the
+ * YUI modules in the Mojito framework.
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context for YUI configuration
+ * @return {object} YUI configuration for Mojito framework
+ */
getYuiConfigFw: function(env, ctx) {
//logger.log('getYuiConfigFw('+env+')');
var modules = {},
- ress, resid, res;
+ ress,
+ resid,
+ res;
- if (! this._fwMeta[env]) {
+ if (!this._fwMeta[env]) {
return {modules: {}};
}
ress = this._getResourceListForContext(this._fwMeta[env], ctx);
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
modules[res.yuiModuleName] = {
- fullpath: ('client' === env) ? res.staticHandlerURL : res.fsPath,
+ fullpath: ('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath,
requires: res.yuiModuleMeta.requires
};
}
@@ -708,24 +834,35 @@ MojitoServer 0.1.0
},
- // TODO DOCS [bug 4647357]
+ /**
+ * Returns the YUI configuration object which tells YUI about the
+ * YUI modules in the application (which aren't part of a mojit).
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context for YUI configuration
+ * @return {object} YUI configuration for the app-level modules
+ */
getYuiConfigApp: function(env, ctx) {
//logger.log('getYuiConfigApp('+env+')');
var modules = {},
- ress, resid, res;
+ ress,
+ resid,
+ res;
- if (! this._appMeta[env]) {
+ if (!this._appMeta[env]) {
return {modules: {}};
}
ress = this._getResourceListForContext(this._appMeta[env], ctx);
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
modules[res.yuiModuleName] = {
- fullpath: ('client' === env) ? res.staticHandlerURL : res.fsPath,
+ fullpath: ('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath,
requires: res.yuiModuleMeta.requires
};
}
@@ -735,18 +872,23 @@ MojitoServer 0.1.0
/*
- * FUTURE: [bug 4647365] Cache the output of this function
- * returns a serializeable object (for passing JSON)
+ * returns a serializeable object used to initialize Mojito on the client
+ *
+ * FUTURE: [Issue 105] Cache the output of this function
* cache key: all of ctx
*
- * @method serializeClientStore
- * @param context {object}
- * @param instance {list} list of instances to deploy to the client
+ * @param context {object} runtime context
+ * @param instance {array} DEPRECATED: list of instances to deploy to the client
* (only instances with IDs will be deployable)
+ * @return {object} object that should be serialized and used to initialize the MojitoClient
*/
serializeClientStore: function(ctx, instances) {
//logger.log('serializeClientStore()');
- var i, id, instance, type, types = {},
+ var i,
+ id,
+ instance,
+ type,
+ types = {},
out = {
appConfig: {},
specs: {}, // instance details
@@ -756,7 +898,7 @@ MojitoServer 0.1.0
out.appConfig = this.getAppConfig(ctx, 'definition');
- for (i=0; i<instances.length; i++) {
+ for (i = 0; i < instances.length; i += 1) {
instance = instances[i];
types[instance.type] = true;
id = instance.id;
@@ -782,24 +924,40 @@ MojitoServer 0.1.0
return out;
},
- listAllMojits: function(env){
-
- var mojitType, list = [];
+
+ /**
+ * Returns a list of all mojit types in the application.
+ *
+ * @param env {string} "client" or "server"
+ * @return {array} list of mojit types
+ */
+ listAllMojits: function(env) {
+ var mojitType,
+ list = [];
for (mojitType in this._mojitMeta[env]) {
if (this._mojitMeta[env].hasOwnProperty(mojitType)) {
list.push(mojitType);
}
}
-
return list;
},
- getAllMojits: function(env, ctx){
- var mojits, mojit, list = {};
+ /**
+ * Returns details about all mojits in the application.
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context
+ * @return {object} keys are mojit type names, values are details about each mojit
+ */
+ getAllMojits: function(env, ctx) {
+
+ var mojits,
+ mojit,
+ list = {};
- if(!ctx){
+ if (!ctx) {
ctx = {};
}
@@ -807,7 +965,8 @@ MojitoServer 0.1.0
for (mojit in mojits) {
if (mojits.hasOwnProperty(mojit)) {
- list[mojits[mojit]] = this.getMojitTypeDetails(env, ctx, mojits[mojit]);
+ list[mojits[mojit]] =
+ this.getMojitTypeDetails(env, ctx, mojits[mojit]);
}
}
@@ -816,11 +975,11 @@ MojitoServer 0.1.0
/*
- * Find the best match for a context, considering context language. Falls
- * back to the first match found if there is no suitable language.
+ * Given a set of known contexts, finds the best match for a runtime context.
+ * Gives special consideration to the "lang" key in the contexts.
*
- * @param {object} currentContext
- * @param {object} contexts a mapping of context key to context
+ * @param currentContext {object} runtime context
+ * @param contexts {object} a mapping of context key to context
* @return {string} null or the context key of the best match
*/
_findBestContext: function(currentContext, contexts) {
@@ -834,7 +993,6 @@ MojitoServer 0.1.0
// Collect languages from matching contexts
// We're done if we find an exact match
-
for (ctxKey in contexts) {
if (contexts.hasOwnProperty(ctxKey)) {
context = contexts[ctxKey];
@@ -852,11 +1010,12 @@ MojitoServer 0.1.0
}
// If no exact match, find the next best language
-
- if (!bestCtxKey && availableLangs && availableLangs.length && currentContext && currentContext.lang) {
- bestLang = this._Y.Intl.lookupBestLang(currentContext.lang, availableLangs);
+ if (!bestCtxKey && availableLangs && availableLangs.length &&
+ currentContext && currentContext.lang) {
+ bestLang = Y.Intl.lookupBestLang(currentContext.lang,
+ availableLangs);
if (bestLang) {
- for (i = 0; i < matchingKeys.length; i++) {
+ for (i = 0; i < matchingKeys.length; i += 1) {
if (contexts[matchingKeys[i]].lang === bestLang) {
bestCtxKey = matchingKeys[i];
break;
@@ -871,65 +1030,98 @@ MojitoServer 0.1.0
},
- // TODO DOCS [bug 4647368]
+ /**
+ * Returns details about a mojit type.
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context
+ * @param mojitType {string} mojit type
+ * @param dest {object} object in which to place the results
+ * @return {object} returns the "dest" parameter, which has had details added to it
+ */
getMojitTypeDetails: function(env, ctx, mojitType, dest) {
//logger.log('getMojitTypeDetails('+env+',ctx,'+mojitType+')');
- var ress, resid, res, name,
- engine, engines = {},
+ var ress,
+ resid,
+ res,
+ name,
+ engine,
+ engines = {},
ctxKey,
- assumeRollups = this._appConfigNC.assumeRollups,
- module;
+ assumeRollups = this._appConfigStatic.assumeRollups,
+ usePrecomputed = -1 !== this._appConfigStatic.yui.
+ dependencyCalculations.indexOf('precomputed'),
+ useOnDemand = -1 !== this._appConfigStatic.yui.
+ dependencyCalculations.indexOf('ondemand'),
+ module,
+ lddf, // lang/datatype-date-format
+ lddfPath;
+
+ if (!usePrecomputed) {
+ useOnDemand = true;
+ }
- if(!dest){
+ if (!dest) {
dest = {};
}
- if (! dest.actions) {
+ if (!dest.actions) {
dest.actions = [];
}
- if (! dest.assets) {
+ if (!dest.assets) {
dest.assets = {};
}
- if (! dest.models) {
+ if (!dest.models) {
dest.models = {};
}
- if (! dest.modelYUIModuleNames) {
+ if (!dest.modelYUIModuleNames) {
dest.modelYUIModuleNames = {};
}
- if (! dest.views) {
+ if (!dest.views) {
dest.views = {};
}
- if (! dest.yui) {
+ if (!dest.yui) {
dest.yui = {config: {}, requires: []};
}
- if (! dest.yui.config) {
+ if (!dest.yui.config) {
dest.yui.config = {modules: {}};
}
- if (! dest.yui.config.modules) {
+ if (!dest.yui.config.modules) {
dest.yui.config.modules = {};
}
- if (! dest.yui.requires) {
+ if (!dest.yui.requires) {
dest.yui.requires = [];
}
+ if (!dest.yui.langs) {
+ dest.yui.langs = {};
+ }
- if ('precomputed' === this._appConfigNC.yui.dependencyCalculations) {
- ctxKey = this._findBestContext(ctx, this._mojitYuiSorted[env][mojitType].contexts);
- dest.yui.requires = this._mojitYuiRequired[env][mojitType][ctxKey];
- dest.yui.sorted = this._mojitYuiSorted[env][mojitType][ctxKey];
- dest.yui.sortedPaths = this._mojitYuiSortedPaths[env][mojitType][ctxKey];
+ if (usePrecomputed) {
+ ctxKey = this._findBestContext(ctx,
+ this._mojitYuiSorted[env][mojitType].contexts);
+ dest.yui.requires =
+ this._mojitYuiRequired[env][mojitType][ctxKey] || [];
+ dest.yui.sorted =
+ this._cloneObj(
+ this._mojitYuiSorted[env][mojitType][ctxKey] || []
+ );
+ dest.yui.sortedPaths =
+ this._cloneObj(
+ this._mojitYuiSortedPaths[env][mojitType][ctxKey] || {}
+ );
}
dest.assetsRoot = this._mojitAssetRoots[mojitType];
- dest.definition = this._getMojitConfig('server', ctx, mojitType, 'definition');
- dest.defaults = this._getMojitConfig('server', ctx, mojitType, 'defaults');
+ dest.definition = this._getMojitConfig('server', ctx, mojitType,
+ 'definition');
+ dest.defaults = this._getMojitConfig('server', ctx, mojitType,
+ 'defaults');
- ress = this._getResourceListForContext(this._mojitMeta[env][mojitType], ctx);
+ ress = this._getResourceListForContext(this._mojitMeta[env][mojitType],
+ ctx);
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
- //console.log('>>>>>>>>>>>>>>>>>>>>>>>> ' + res.type +':'+res.yuiModuleName);
- //console.log(res);
-
res = ress[resid];
if (res.type === 'action') {
@@ -958,7 +1150,7 @@ MojitoServer 0.1.0
}
if (res.type === 'binder') {
- if (! dest.views[res.name]) {
+ if (!dest.views[res.name]) {
dest.views[res.name] = {};
}
dest.views[res.name]['binder-url'] = res.staticHandlerURL;
@@ -966,22 +1158,25 @@ MojitoServer 0.1.0
if (assumeRollups) {
dest.views[res.name]['binder-path'] = res.rollupURL;
} else {
- dest.views[res.name]['binder-path'] = res.staticHandlerURL;
+ dest.views[res.name]['binder-path'] =
+ res.staticHandlerURL;
}
} else {
dest.views[res.name]['binder-path'] = res.fsPath;
}
dest.views[res.name]['binder-module'] = res.yuiModuleName;
- dest.views[res.name]['binder-yui-sorted'] = res.yuiSortedPaths;
+ dest.views[res.name]['binder-yui-sorted'] =
+ res.yuiSortedPaths;
if ('server' === env) {
- // don't do any other type of server-side processing for the binder
- // ESPECIALLY don't add it to dest.yui.*
+ // don't do any other type of server-side processing for
+ // the binder ESPECIALLY don't add it to dest.yui.*
continue;
}
}
if (res.type === 'controller') {
- // We need the YUI Module name of the contoller so we can select a language for it
+ // We need the YUI Module name of the contoller so we can
+ // select a language for it
dest.controllerModuleName = res.yuiModuleName;
if (env === 'client') {
if (assumeRollups) {
@@ -994,11 +1189,6 @@ MojitoServer 0.1.0
}
}
- // TODO: why is this commented-out but not deleted?
- //if (res.type === 'yui-lang') {
- // dest.lang = ('client' === env) ? res.staticHandlerURL : res.fsPath;
- //}
-
if (res.type === 'model') {
if (env === 'client') {
if (assumeRollups) {
@@ -1011,19 +1201,22 @@ MojitoServer 0.1.0
}
if (res.yuiModuleName) {
dest.modelYUIModuleNames[res.yuiModuleName] = true;
-// logger.log("Processing Models:" + res.name + ":" + res.yuiModuleName, 'mojito', NAME);
+ //logger.log("Processing Models:" + res.name + ":" +
+ // res.yuiModuleName, 'mojito', NAME);
}
}
if (res.type === 'view') {
- if (! dest.views[res.name]) {
+ if (!dest.views[res.name]) {
dest.views[res.name] = {};
}
if (env === 'client') {
if (assumeRollups) {
- dest.views[res.name]['content-path'] = res.rollupURL;
+ dest.views[res.name]['content-path'] =
+ res.rollupURL;
} else {
- dest.views[res.name]['content-path'] = res.staticHandlerURL;
+ dest.views[res.name]['content-path'] =
+ res.staticHandlerURL;
}
} else {
dest.views[res.name]['content-path'] = res.fsPath;
@@ -1032,11 +1225,18 @@ MojitoServer 0.1.0
engines[res.viewEngine] = true;
}
+ if (res.type === 'yui-lang') {
+ dest.yui.langs[res.langCode] = res.yuiModuleName;
+ }
+
if (res.yuiModuleName) {
if (res.addonType === 'view-engines') {
// we'll only load the viewEngines that we need
continue;
}
+ if (useOnDemand) {
+ dest.yui.requires.push(res.yuiModuleName);
+ }
if (('fw' === res.source) || ('app' === res.source)) {
continue;
}
@@ -1046,18 +1246,22 @@ MojitoServer 0.1.0
};
if (env === 'client') {
if (assumeRollups) {
- dest.yui.config.modules[res.yuiModuleName].fullpath = res.rollupURL;
+ dest.yui.config.modules[res.yuiModuleName
+ ].fullpath = res.rollupURL;
} else {
- dest.yui.config.modules[res.yuiModuleName].fullpath = res.staticHandlerURL;
+ dest.yui.config.modules[res.yuiModuleName
+ ].fullpath = res.staticHandlerURL;
}
} else {
- dest.yui.config.modules[res.yuiModuleName].fullpath = res.fsPath;
+ dest.yui.config.modules[res.yuiModuleName].fullpath =
+ res.fsPath;
}
// If we have "lang" use it
if (this._mojitLangs[res.yuiModuleName]) {
this._mojitLangs[res.yuiModuleName].sort();
- dest.yui.config.modules[res.yuiModuleName].lang = this._mojitLangs[res.yuiModuleName];
+ dest.yui.config.modules[res.yuiModuleName].lang =
+ this._mojitLangs[res.yuiModuleName];
}
}
}
@@ -1065,11 +1269,53 @@ MojitoServer 0.1.0
for (engine in engines) {
if (engines.hasOwnProperty(engine)) {
resid = 'addon-view-engines-' + engine;
- res = this._getContextualizedResource(this._mojitMeta[env][mojitType], ctx, resid);
+ res = this._getContextualizedResource(
+ this._mojitMeta[env][mojitType],
+ ctx,
+ resid
+ );
dest.yui.config.modules[res.yuiModuleName] = {
- fullpath: ('client' === env) ? res.staticHandlerURL : res.fsPath,
+ fullpath: ('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath,
requires: res.yuiModuleMeta.requires || []
};
+ if (useOnDemand) {
+ dest.yui.requires.push(res.yuiModuleName);
+ }
+ }
+ }
+
+ // We need to include -all- the langs since we don't know which will
+ // actually be used. dispatch() will cull that down to the right one.
+ if (usePrecomputed) {
+ for (name in dest.yui.langs) {
+ if (dest.yui.langs.hasOwnProperty(name)) {
+ module = dest.yui.langs[name];
+ lddf = 'lang/datatype-date-format_' + (name || 'en');
+ if (dest.yui.sortedPaths[lddf]) {
+ lddfPath = dest.yui.sortedPaths[lddf].replace('_' +
+ name, '_MOJITOPLACEHOLDER');
+ }
+ if (!dest.yui.sortedPaths[module]) {
+ dest.yui.sorted.push(module);
+ dest.yui.sortedPaths[module] =
+ dest.yui.config.modules[module].fullpath;
+ }
+ }
+ }
+ if (lddfPath) {
+ for (name in dest.yui.langs) {
+ if (dest.yui.langs.hasOwnProperty(name)) {
+ lddf = 'lang/datatype-date-format_' + (name || 'en');
+ if (!dest.yui.sortedPaths[lddf]) {
+ dest.yui.sorted.push(lddf);
+ dest.yui.sortedPaths[lddf] =
+ lddfPath.replace('MOJITOPLACEHOLDER',
+ (name || 'en'));
+ }
+ }
+ }
}
}
@@ -1077,16 +1323,25 @@ MojitoServer 0.1.0
},
- // TODO DOCS [bug 4647383]
+ /**
+ * Returns details on how to make rollups for app-level resources.
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context
+ * @return {object} object describing where to put the rollup and what it should contain
+ */
getRollupsApp: function(env, ctx) {
- var dest = libpath.join(this._root, 'rollup.'+env+'.js'),
+ var dest = libpath.join(this._root, 'rollup.' + env + '.js'),
srcs = [],
- ress, resid, res;
+ ress,
+ resid,
+ res;
+
ress = this._getResourceListForContext(this._fwMeta[env], ctx);
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
srcs.push(res.fsPath);
@@ -1097,7 +1352,7 @@ MojitoServer 0.1.0
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
srcs.push(res.fsPath);
@@ -1111,38 +1366,55 @@ MojitoServer 0.1.0
},
- // TODO DOCS [bug 4647385]
- // This example comes from GSG5.
- // { FlickrDetail:
- // dest: '/blah/blah/mojits/FlickrDetail/rollup.client.js'
- // srcs: [
- // '/blah/blah/mojits/FlickrDetail/controller.common.js',
- // '/blah/blah/mojits/FlickrDetail/binders/index.js',
- // '/blah/blah/mojits/FlickrDetail/lang/FlickrDetail_de.js',
- // '/blah/blah/mojits/FlickrDetail/lang/FlickrDetail_en-US.js'
- // ]
- // }
+ /**
+ * Returns details on how to make rollups for mojit-level resources.
+ *
+ * This example comes from GSG5.
+ * { FlickrDetail:
+ * dest: '/blah/blah/mojits/FlickrDetail/rollup.client.js'
+ * srcs: [
+ * '/blah/blah/mojits/FlickrDetail/controller.common.js',
+ * '/blah/blah/mojits/FlickrDetail/binders/index.js',
+ * '/blah/blah/mojits/FlickrDetail/lang/FlickrDetail_de.js',
+ * '/blah/blah/mojits/FlickrDetail/lang/FlickrDetail_en-US.js'
+ * ]
+ * }
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context
+ * @return {object} object describing where to put the rollup and what it should contain
+ */
getRollupsMojits: function(env, ctx) {
- var mojitName, ress, resid, res,
- dest, srcs, rollups = {},
+ var mojitName,
+ ress,
+ resid,
+ res,
+ dest,
+ srcs,
+ rollups = {},
mojitoMojits = libpath.join(mojitoRoot, 'mojits');
+
for (mojitName in this._mojitMeta[env]) {
if (this._mojitMeta[env].hasOwnProperty(mojitName)) {
srcs = [];
- ress = this._getResourceListForContext(this._mojitMeta[env][mojitName], ctx);
+ ress = this._getResourceListForContext(
+ this._mojitMeta[env][mojitName],
+ ctx
+ );
for (resid in ress) {
if (ress.hasOwnProperty(resid)) {
res = ress[resid];
if (res.source !== 'mojit') {
continue;
}
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
continue;
}
srcs.push(res.fsPath);
}
}
- dest = libpath.join(this._mojitPaths[mojitName], 'rollup.'+env+'.js');
+ dest = libpath.join(this._mojitPaths[mojitName], 'rollup.' +
+ env + '.js');
if (dest.indexOf(mojitoMojits) === 0) {
// we shouldn't write to the mojits that ship with Mojito
dest = false;
@@ -1159,40 +1431,61 @@ MojitoServer 0.1.0
},
- // TODO DOCS 2011-07-21
- // This example comes from (a modified) GSG5.
- // [ {
- // context: { device: 'iphone' },
- // mojitName: 'FlickrDetail',
- // yuiModuleName: 'inlinecss/FlickrDetail',
- // dest: '/blah/blah/mojits/FlickrDetail/autoload/compiled/css.iphone.client.js',
- // srcs: {
- // '/static/FlickrDetail/assets/index.css': '/blah/blah/blah/mojits/FlickrDetail/assets/index.iphone.css',
- // '/static/FlickrDetail/assets/message.css': '/blah/blah/blah/mojits/FlickrDetail/assets/message.css'
- // }
- // ]
+ /**
+ * Returns details on how to make inline CSS for mojits.
+ *
+ * This example comes from (a modified) GSG5.
+ * [ {
+ * context: { device: 'iphone' },
+ * mojitName: 'FlickrDetail',
+ * yuiModuleName: 'inlinecss/FlickrDetail',
+ * dest: '/blah/mojits/FlickrDetail/autoload/compiled' +
+ * '/css.iphone.client.js',
+ * srcs: {
+ * '/static/FlickrDetail/assets/index.css':
+ * ' /blah/mojits/FlickrDetail/assets/index.iphone.css',
+ * '/static/FlickrDetail/assets/message.css':
+ * ' /blah/mojits/FlickrDetail/assets/message.css'
+ * }
+ * ]
+ *
+ * @param env {string} "client" or "server"
+ * @param ctxFilter {object} (optional) runtime context to restrict results to
+ * @return {array} object describing where to put the inline CSS file and what it should contain
+ */
getInlineCssMojits: function(env, ctxFilter) {
- var mojitName, ctxKey, ctx, ress, resid, res, selector,
- dest, srcs, inlines = [];
+ var mojitName,
+ ctxKey,
+ ctx,
+ ress,
+ resid,
+ res,
+ selector,
+ dest,
+ srcs,
+ inlines = [],
mojitoMojits = libpath.join(mojitoRoot, 'mojits');
// This will make our test later a little easier.
- if (typeof(ctxFilter) === "object" && !Object.keys(ctxFilter).length) {
+ if (typeof ctxFilter === 'object' && !Object.keys(ctxFilter).length) {
ctxFilter = null;
}
for (mojitName in this._mojitMeta[env]) {
if (this._mojitMeta[env].hasOwnProperty(mojitName)) {
for (ctxKey in this._mojitMeta[env][mojitName].contexts) {
- if (this._mojitMeta[env][mojitName].contexts.hasOwnProperty(ctxKey)) {
+ if (this._mojitMeta[env][mojitName
+ ].contexts.hasOwnProperty(ctxKey)) {
ctx = this._mojitMeta[env][mojitName].contexts[ctxKey];
if (ctxFilter && !this._matchContext(ctx, ctxFilter)) {
continue;
}
- ress = this._cloneObj(this._mojitMeta[env][mojitName]['*']);
+ ress = this._cloneObj(this._mojitMeta[env
+ ][mojitName]['*']);
if ('*' !== ctxKey) {
- ress = this._mergeRecursive(ress, this._mojitMeta[env][mojitName][ctxKey]);
+ ress = this._mergeRecursive(ress,
+ this._mojitMeta[env][mojitName][ctxKey]);
}
srcs = [];
for (resid in ress) {
@@ -1201,17 +1494,20 @@ MojitoServer 0.1.0
if (res.source !== 'mojit') {
continue;
}
- if ((res.type !== 'asset') || (res.assetType !== 'css')) {
+ if ((res.type !== 'asset') ||
+ (res.assetType !== 'css')) {
continue;
}
srcs[res.staticHandlerURL] = res.fsPath;
}
}
selector = this._selectorFromContext(ctx);
- dest = 'autoload/compiled/inlinecss' + (selector ? '.'+selector : '') + '.common.js';
+ dest = 'autoload/compiled/inlinecss' + (selector ? '.' +
+ selector : '') + '.common.js';
dest = libpath.join(this._mojitPaths[mojitName], dest);
if (dest.indexOf(mojitoMojits) === 0) {
- // we shouldn't write to the mojits that ship with Mojito
+ // we shouldn't write to the mojits that ship with
+ // Mojito
continue;
}
if (Object.keys(srcs).length) {
@@ -1235,7 +1531,11 @@ MojitoServer 0.1.0
// ===========================================
// ================= PRIVATE =================
- _readAppConfigNC: function() {
+ /*
+ * the "static" version of the application.json is the version that has
+ * the context applied that was given at server-start time.
+ */
+ _readAppConfigStatic: function() {
var path = libpath.join(this._root, 'application.json'),
config = this._cloneObj(this._fwConfig.appConfigBase),
appConfig;
@@ -1249,55 +1549,120 @@ MojitoServer 0.1.0
},
- // TODO DREW 2011-06-16: [bug 4647391] work out story for this stage
+ /*
+ * Read the application's dimensions.json file for YCB processing. If not
+ * available, fall back to the framework's default dimensions.json.
+ *
+ * @return {array}
+ */
+ _readYcbDimensions: function() {
+ var libpath = this._libs.path,
+ path = libpath.join(this._root, 'dimensions.json'),
+ dims;
+
+ if (!libpath.existsSync(path)) {
+ path = libpath.join(mojitoRoot, 'dimensions.json');
+ }
+
+ dims = this._readConfigJSON(path);
+ if (!this._isValidYcbDimensions(dims)) {
+ throw new Error('Invalid dimensions.json: ' + path);
+ }
+ return dims;
+ },
+
+
+ /*
+ * Perform some light validation for YCB dimensions JSON objects. It should
+ * look something like this:
+ * [{
+ * "dimensions": [
+ * "dim1": {},
+ * "dim2": {},
+ * ...
+ * ]
+ * }]
+ *
+ * @param {array} dimensions
+ * @return {boolean}
+ */
+ _isValidYcbDimensions: function(dims) {
+ var isArray = Y.Lang.isArray;
+
+ return isArray(dims) &&
+ dims.length === 1 &&
+ isArray(dims[0].dimensions) &&
+ dims[0].dimensions.length > 0;
+ },
+
+
+ // TODO: [Issue 109] work out story for this stage.
+ /*
+ * preload metadata about all resources in the application (and Mojito framework)
+ */
_preloadMeta: function() {
- var i, path, realDirs = [];
+ var i,
+ path,
+ realDirs = [];
+
+ // All the contents of the app/ directory are "app-level" resource
+ // (resources for mojits which are applied to -all- mojits)
+ // (A better name might have been "global" instead of "app-level".)
+ this._preloadDirMojit('fw', false, libpath.join(mojitoRoot, 'app'));
- this._preloadDirMojit('fw', false, libpath.join(mojitoRoot,'app'));
- this._preloadDirMojits(false, libpath.join(mojitoRoot,'mojits'));
+ this._preloadDirMojits(false, libpath.join(mojitoRoot, 'mojits'));
- this._preloadFileConfig(this._preload.appMeta, 'app', null, libpath.join(this._root,'application.json'), 'definition');
+ this._preloadFileConfig(this._preload.appMeta, 'app', null,
+ libpath.join(this._root, 'application.json'), 'definition');
+ // read app-level resources
this._preloadDirMojit('app', false, this._root);
- // TODO DREW 2011-06-16: [bug 4647391] split loops into separate functions (for readability)
+ // TODO: [Issue 109] split loops into separate functions (for readability).
- for (i=0; i<this._appConfigNC.routesFiles.length; i++) {
- path = this._appConfigNC.routesFiles[i];
+ // load routes file(s)
+ for (i = 0; i < this._appConfigStatic.routesFiles.length; i += 1) {
+ path = this._appConfigStatic.routesFiles[i];
if ('/' !== path.charAt(0)) {
path = libpath.join(this._root, path);
}
- this._preloadFileConfig(this._preload.appMeta, 'app', null, path, 'routes');
+ this._preloadFileConfig(this._preload.appMeta, 'app', null, path,
+ 'routes');
}
- for (i=0; i<this._appConfigNC.mojitsDirs.length; i++) {
- path = this._appConfigNC.mojitsDirs[i];
+ // load mojitsDirs
+ for (i = 0; i < this._appConfigStatic.mojitsDirs.length; i += 1) {
+ path = this._appConfigStatic.mojitsDirs[i];
if ('/' !== path.charAt(0)) {
path = libpath.join(this._root, path);
}
libglob.globSync(path, {}, realDirs);
}
- if (! realDirs.length) {
- throw new Error('Failed to find any mojitsDirs matching ' + this._appConfigNC.mojitsDirs.join(', '));
+ if (!realDirs.length) {
+ throw new Error('Failed to find any mojitsDirs matching ' +
+ this._appConfigStatic.mojitsDirs.join(', '));
}
- for (i=0; i<realDirs.length; i++) {
+ for (i = 0; i < realDirs.length; i += 1) {
path = realDirs[i];
this._preloadDirMojits(true, path);
}
- if (this._appConfigNC.mojitDirs && this._appConfigNC.mojitDirs.length) {
+ // load mojitDirs
+ if (this._appConfigStatic.mojitDirs &&
+ this._appConfigStatic.mojitDirs.length) {
realDirs = [];
- for (i=0; i<this._appConfigNC.mojitDirs.length; i++) {
- path = this._appConfigNC.mojitDirs[i];
+ for (i = 0; i < this._appConfigStatic.mojitDirs.length; i += 1) {
+ path = this._appConfigStatic.mojitDirs[i];
if ('/' !== path.charAt(0)) {
path = libpath.join(this._root, path);
}
libglob.globSync(path, {}, realDirs);
}
- if (! realDirs.length) {
- throw new Error('Failed to find any mojitDirs matching ' + this._appConfigNC.mojitDirs.join(', '));
+ if (!realDirs.length) {
+ throw new Error('Failed to find any mojitDirs matching ' +
+ this._appConfigStatic.mojitDirs.join(', '));
}
- for (i=0; i<realDirs.length; i++) {
+ for (i = 0; i < realDirs.length; i += 1) {
path = realDirs[i];
this._preloadDirMojit('mojit', true, path);
}
@@ -1305,16 +1670,23 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for mojits in a directory
+ */
_preloadDirMojits: function(readConfig, fullpath) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
- //logger.log('----------------------- preloadDirMojits(' + readConfig + ') -- ' + fullpath);
- var children = this._libs.fs.readdirSync(fullpath),
- i, childName, childPath, mojitName;
- for (i=0; i<children.length; i++) {
+ //logger.log('----------------------- preloadDirMojits(' + readConfig +
+ // ') -- ' + fullpath);
+ var children = this._sortedReaddirSync(fullpath),
+ i,
+ childName,
+ childPath;
+
+ for (i = 0; i < children.length; i += 1) {
childName = children[i];
- if ('.' === childName.substring(0,1)) {
+ if ('.' === childName.substring(0, 1)) {
continue;
}
childPath = libpath.join(fullpath, childName);
@@ -1323,78 +1695,114 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for resources in a directory which contains a single mojit
+ *
+ * @param source {string} "fw", "app" or "mojit"
+ * @param readConfig {boolean} whether to read the config files in the mojit
+ * @param fullpath {string}
+ * @return {nothing} results stored in this object
+ */
_preloadDirMojit: function(source, readConfig, fullpath) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
-// logger.log('----------------------- preloadDirMojit(' + source + ',' + readConfig + ') -- ' + fullpath);
- var packageJson, definitionJson, mojitType, dest,
- appConfig, urlPrefix, url;
+ //logger.log('----------------------- preloadDirMojit(' +
+ // source + ',' + readConfig + ') -- ' + fullpath);
+ var packageJson,
+ definitionJson,
+ mojitType,
+ dest,
+ appConfig,
+ urlPrefix,
+ url;
if (readConfig && 'mojit' === source) {
- packageJson = this._readMojitConfigFile(libpath.join(fullpath,'package.json'), false);
+ packageJson = this._readMojitConfigFile(libpath.join(fullpath,
+ 'package.json'), false);
mojitType = packageJson.name;
- definitionJson = this._readMojitConfigFile(libpath.join(fullpath,'definition.json'), true);
+ definitionJson = this._readMojitConfigFile(libpath.join(fullpath,
+ 'definition.json'), true);
if (definitionJson.appLevel) {
source = 'app';
}
}
- if (! mojitType) {
+ if (!mojitType) {
mojitType = libpath.basename(fullpath);
}
this._mojitPaths[mojitType] = fullpath;
switch (source) {
- case 'fw':
- dest = this._preload.fwMeta;
- break;
- case 'app':
- dest = this._preload.appMeta;
- break;
- case 'mojit':
- if (! this._preload.mojitMeta[mojitType]) {
- this._preload.mojitMeta[mojitType] = {};
- }
- dest = this._preload.mojitMeta[mojitType];
- break;
+ case 'fw':
+ dest = this._preload.fwMeta;
+ break;
+ case 'app':
+ dest = this._preload.appMeta;
+ break;
+ case 'mojit':
+ if (!this._preload.mojitMeta[mojitType]) {
+ this._preload.mojitMeta[mojitType] = {};
+ }
+ dest = this._preload.mojitMeta[mojitType];
+ break;
}
- // TODO DREW 2011-06-16: [bug 4647391] refactor to return semantics
+ // TODO: [Issue 109] refactor to return semantics.
if (readConfig && 'fw' !== source) {
- if (! this._mojitoVersionMatch(packageJson, this._version)) {
- logger.log('Mojito version mismatch: mojit skipped in "' + fullpath + '"', 'warn', NAME);
+ if (!this._mojitoVersionMatch(packageJson, this._version)) {
+ logger.log('Mojito version mismatch: mojit skipped in "' +
+ fullpath + '"', 'warn', NAME);
return;
}
+ // TODO: this might benefit from passing mojitType to it
this._mojitPackageAsAsset(packageJson, fullpath);
}
if ('mojit' === source) {
- // TODO DREW 2011-06-16: [bug 4647391] make this more future-proof
- this._preloadFileController(dest, source, mojitType, libpath.join(fullpath,'controller.common.js'), fullpath);
- this._preloadFileController(dest, source, mojitType, libpath.join(fullpath,'controller.client.js'), fullpath);
- this._preloadFileController(dest, source, mojitType, libpath.join(fullpath,'controller.server.js'), fullpath);
+ // TODO: [Issue 109] make this more future-proof.
+ this._preloadFileController(dest, source, mojitType,
+ libpath.join(fullpath, 'controller.common.js'), fullpath);
+ this._preloadFileController(dest, source, mojitType,
+ libpath.join(fullpath, 'controller.client.js'), fullpath);
+ this._preloadFileController(dest, source, mojitType,
+ libpath.join(fullpath, 'controller.server.js'), fullpath);
}
if (readConfig) {
- this._preloadFileConfig(dest, source, mojitType, libpath.join(fullpath,'definition.json'), 'definition');
- this._preloadFileConfig(dest, source, mojitType, libpath.join(fullpath,'defaults.json'), 'defaults');
- }
-
- // TODO DREW 2011-06-16: [bug 4647391] pass actual functions (instead of function names)
- // TODO DREW 2011-06-16: [bug 4647391] refactor: first, use a method that returns all files in dir (with ext), then call _preloadFileX on them
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'actions'), false, '_preloadFileAction');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'addons'), true, '_preloadFileAddon');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'assets'), true, '_preloadFileAsset');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'autoload'), true, '_preloadFileAutoload');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'binders'), true, '_preloadFileBinder');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'lang'), false, '_preloadFileLang');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'models'), false, '_preloadFileModel');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'views'), true, '_preloadFileView');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'specs'), false, '_preloadFileSpec');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'tests'), false, '_preloadFileAutoload');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'tests/models'), false, '_preloadFileAutoload');
- this._preloadDir(dest, source, mojitType, libpath.join(fullpath,'tests/binders'), false, '_preloadFileAutoload');
+ this._preloadFileConfig(dest, source, mojitType,
+ libpath.join(fullpath, 'definition.json'), 'definition');
+ this._preloadFileConfig(dest, source, mojitType,
+ libpath.join(fullpath, 'defaults.json'), 'defaults');
+ }
+
+ // TODO: [Issue 109] pass actual functions (instead of function names)
+ // TODO: [Issue 109] refactor: first, use a method that returns all
+ // files in dir (with ext), then call _preloadFileX on them
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'actions'), false, '_preloadFileAction');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'addons'), true, '_preloadFileAddon');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'assets'), true, '_preloadFileAsset');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'autoload'), true, '_preloadFileAutoload');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'binders'), true, '_preloadFileBinder');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'lang'), false, '_preloadFileLang');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'models'), false, '_preloadFileModel');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'views'), true, '_preloadFileView');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'specs'), false, '_preloadFileSpec');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'tests'), false, '_preloadFileAutoload');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'tests/models'), false, '_preloadFileAutoload');
+ this._preloadDir(dest, source, mojitType, libpath.join(fullpath,
+ 'tests/binders'), false, '_preloadFileAutoload');
/*
* Here we are making a URL for the expanded "definition.json"
@@ -1403,12 +1811,14 @@ MojitoServer 0.1.0
*/
if ('mojit' === source) {
- // TODO 2011-06-16: [bug 4647391] re-use logic from _calcStaticHandlerURL() for prefix (so that all options are supported)
+ // TODO: [Issue 109] re-use logic from
+ // _calcStaticHandlerURL() for prefix (so that all options are
+ // supported)
- appConfig = this._appConfigNC.staticHandling || {};
+ appConfig = this._appConfigStatic.staticHandling || {};
urlPrefix = '/static';
if (typeof appConfig.prefix !== 'undefined') {
- urlPrefix = appConfig.prefix ? '/'+appConfig.prefix : '';
+ urlPrefix = appConfig.prefix ? '/' + appConfig.prefix : '';
}
// Add our definition to the Dynamic URLs list
@@ -1418,43 +1828,75 @@ MojitoServer 0.1.0
},
- _preloadDir: function(dest, source, mojitType, fullpath, recurse, fileHandler, root) {
- if (! this._libs.path.existsSync(fullpath)) {
+ /*
+ * preloads metadata for resources in a directory
+ *
+ * @param dest {object} where results shoulg go (passed to fileHandler)
+ * @param source {string} where the resource is coming from (passed to fileHandler)
+ * @param mojitType {string} name of mojit type (passed to fileHandler)
+ * @param fullpath {string} path of directory
+ * @param recurse {boolean} whether to recurse into subdirectories
+ * @param fileHandler {string} name of method on this object to call on each file
+ * @param root {string} when recursing, fullpath of first call (passed to fileHandler)
+ * @return {nothing} calls fileHandler to do the actual insteresting work
+ */
+ _preloadDir: function(dest, source, mojitType, fullpath, recurse,
+ fileHandler, root) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
- //logger.log('----------------------- preloadDir(' + recurse + ',' + fileHandler + ') -- ' + fullpath);
- var children = this._libs.fs.readdirSync(fullpath),
- i, childName, childPath, childStat;
- if (! root) {
+ //logger.log('----------------------- preloadDir(' + recurse + ',' +
+ // fileHandler + ') -- ' + fullpath);
+ var children = this._sortedReaddirSync(fullpath),
+ i,
+ childName,
+ childPath,
+ childStat;
+
+ if (!root) {
root = fullpath;
}
- for (i=0; i<children.length; i++) {
+ for (i = 0; i < children.length; i += 1) {
childName = children[i];
- if ('.' === childName.substring(0,1)) {
+ if ('.' === childName.substring(0, 1)) {
continue;
}
childPath = libpath.join(fullpath, childName);
childStat = this._libs.fs.statSync(childPath);
if (childStat.isFile()) {
this[fileHandler](dest, source, mojitType, childPath, root);
- }
- else if (recurse && childStat.isDirectory()) {
- this._preloadDir(dest, source, mojitType, childPath, recurse, fileHandler, root);
+ } else if (recurse && childStat.isDirectory()) {
+ this._preloadDir(dest, source, mojitType, childPath, recurse,
+ fileHandler, root);
}
}
},
+ /*
+ * preloads metadata for a resource which is an action
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileAction: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
- //logger.log('----------------------- preloadFileActions -- ' + fullpath);
+ //logger.log('----------------------- preloadFileActions -- ' +
+ // fullpath);
var resid, res = {},
pathParts = this._parsePath(fullpath, 'server', dir);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'action';
res.fsPath = fullpath;
@@ -1468,16 +1910,30 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is an addon
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileAddon: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
//logger.log('----------------------- preloadFileAddon -- ' + fullpath);
- var resid, res = {},
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'server', dir);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'addon';
res.addonType = libpath.dirname(pathParts.name);
@@ -1492,16 +1948,31 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is an asset
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileAsset: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
//logger.log('----------------------- preloadFileAsset -- ' + fullpath);
- var resid, res = {},
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'common', dir);
+
// All assets are "common", and we want to support the
// shortcut of "assets/foo.iphone.css".
pathParts.affinity = 'common';
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'asset';
res.fsPath = fullpath;
@@ -1516,39 +1987,56 @@ MojitoServer 0.1.0
},
- // This function process all subdirectories using the same logic, so that all files within
- // an "autoload" directory will be processed.
+ /*
+ * preloads metadata for a resource which is an "autoload"
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileAutoload: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
-// logger.log('----------------------- preloadFileAutoload -- ' + fullpath);
- var resid, res = {},
+ //logger.log('----------------------- preloadFileAutoload -- ' +
+ // fullpath);
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'server', dir);
+
if (pathParts.affinity.type) {
- // This allows the app config to specify that some "client" affinity code should not be deployed
- // if it has an "optional" subtype
- if (this._appConfigNC.deferAllOptionalAutoloads && pathParts.affinity.type === 'optional') {
+ // This allows the app config to specify that some "client" affinity
+ // code should not be deployed if it has an "optional" subtype
+ if (this._appConfigStatic.deferAllOptionalAutoloads &&
+ pathParts.affinity.type === 'optional') {
pathParts.affinity = 'none';
- }
- // this filters tests from being included with deployed applications
- else if (pathParts.affinity.type === 'tests' && this._appConfigNC.env !== 'test') {
+ } else if (pathParts.affinity.type === 'tests' &&
+ this._appConfigStatic.env !== 'test') {
+ // this filters tests from being included with deployed
+ // applications
pathParts.affinity = 'none';
}
}
if ('none' === pathParts.affinity) {
return;
}
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
+
res.type = 'yui-module';
res.fsPath = fullpath;
res.source = source;
this._precalcYuiModule(res);
- if (! res.yuiModuleName) {
+ if (!res.yuiModuleName) {
if ('.js' === pathParts.ext) {
- throw new Error("Expected YUI3 module in " + fullpath);
+ throw new Error('Expected YUI3 module in ' + fullpath);
}
return;
}
@@ -1558,16 +2046,32 @@ MojitoServer 0.1.0
this._preloadSetDest(dest, resid, res, pathParts);
},
+
+ /*
+ * preloads metadata for a resource which is a binder
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileBinder: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
- //logger.log('----------------------- preloadFileBinder -- ' + fullpath);
- var resid, res = {},
+ //logger.log('----------------------- preloadFileBinder -- ' +
+ // fullpath);
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'client', dir);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'binder';
res.fsPath = fullpath;
@@ -1581,13 +2085,28 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is a configuration file
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileConfig: function(dest, source, mojitType, fullpath, type) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
- //logger.log('----------------------- preloadFileConfig(' + type + ') -- ' + fullpath);
- var resid, res = {},
+ //logger.log('----------------------- preloadFileConfig(' + type +
+ // ') -- ' + fullpath);
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'server', null);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'config';
res.configType = type;
@@ -1602,16 +2121,31 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is a controller
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileController: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
- //logger.log('----------------------- preloadFileController -- ' + fullpath);
- var resid, res = {},
+ //logger.log('----------------------- preloadFileController -- ' +
+ // fullpath);
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'server', dir);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'controller';
res.fsPath = fullpath;
@@ -1624,47 +2158,63 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is a language bundle
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileLang: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
//logger.log('----------------------- preloadFileLang -- ' + fullpath);
- var resid, res = {},
+ var resid,
+ res = {},
pathParts = {},
- matches, code;
+ matches,
+ code;
// YUI-intl doesn't support our ".affinity." filename syntax.
pathParts.ext = libpath.extname(fullpath);
pathParts.shortpath = libpath.basename(fullpath, pathParts.ext);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
// There is not a "lang" for the default language file
matches = pathParts.shortpath.match(/^([^_]+)(?:_([^_.]+))?$/) || [];
- // TODO: [bug 4647407] Check this as we could have names with underscores
- // YUI-intl expects default language to have no code on the filename.
- // (In practice it's 'en', but don't assume that here.)
- code = matches[2] || "";
+ // TODO: [Issue 10] Check this as we could have names with
+ // underscores YUI-intl expects default language to have no code on the
+ // filename. (In practice it's 'en', but don't assume that here.)
+ code = matches[2] || '';
res.type = 'yui-lang';
res.fsPath = fullpath;
res.source = source;
+ res.langCode = code;
this._precalcYuiModule(res);
this._precalcStaticURL(res, mojitType);
resid = 'yui-lang-' + pathParts.shortpath;
// Check the mojitType has a "lang" entry
- if(!this._mojitLangs[mojitType]){
+ if (!this._mojitLangs[mojitType]) {
this._mojitLangs[mojitType] = [];
}
// If we have a "lang" code add it to the list
- if(code){
+ if (code) {
this._mojitLangs[mojitType].push(code);
}
-
+
pathParts.affinity = 'common';
pathParts.contextKey = 'lang=' + code;
pathParts.contextParts = {lang: code};
@@ -1672,16 +2222,30 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is a model
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileModel: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
if ('.js' !== libpath.extname(fullpath)) {
return;
}
//logger.log('----------------------- preloadFileModel -- ' + fullpath);
- var resid, res = {},
+ var resid,
+ res = {},
pathParts = this._parsePath(fullpath, 'server', dir);
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
res.type = 'model';
res.fsPath = fullpath;
@@ -1695,13 +2259,81 @@ MojitoServer 0.1.0
},
+ /*
+ * preloads metadata for a resource which is a mojit instance specification
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
+ _preloadFileSpec: function(dest, source, mojitType, fullpath, dir) {
+ var name,
+ url,
+ config,
+ appConfig,
+ prefix;
+
+ if (!this._libs.path.existsSync(fullpath)) {
+ return;
+ }
+
+ appConfig = this._appConfigStatic.staticHandling || {};
+ prefix = '/static';
+ if (typeof appConfig.prefix !== 'undefined') {
+ prefix = appConfig.prefix ? '/' + appConfig.prefix : '';
+ }
+
+ // TODO: this might benefit from using _parsePath()
+
+ // Set the URL of the spec
+ url = prefix + '/' + mojitType + '/specs/' + libpath.basename(fullpath);
+
+ // Set the name to the mojit type (this is for namespacing)
+ name = mojitType;
+
+ // If the filename is not "default" add it to the spec name
+ if (libpath.basename(fullpath) !== 'default.json') {
+ name += ':' +
+ libpath.basename(fullpath).split('.').slice(0, -1).join('.');
+ }
+
+ config = this._readConfigYCB({}, fullpath);
+
+ // Just incase this has not been made
+ if (!this._appConfigStatic.specs) {
+ this._appConfigStatic.specs = {};
+ }
+
+ // Add our spec to the specs map
+ this._appConfigStatic.specs[name] = config;
+
+ // Add our spec to the Dynamic URLs list
+ this._dynamicURLs[url] = fullpath;
+ },
+
+
+ /*
+ * preloads metadata for a resource which is a view
+ *
+ * @param dest {object} where to store the metadata of the resource
+ * @param source {string} "fw", "app", or "mojit"
+ * @param mojitType {string} name of mojit type, might be null
+ * @param fullpath {string} path to resource
+ * @param dir {string} base directory of resource type
+ * @return {nothing} results stored in this object
+ */
_preloadFileView: function(dest, source, mojitType, fullpath, dir) {
- if (! this._libs.path.existsSync(fullpath)) {
+ if (!this._libs.path.existsSync(fullpath)) {
return;
}
//logger.log('----------------------- preloadFileView -- ' + fullpath);
- var resid, res = {},
- parts, pathParts = {affinity: 'common', contextKey: '*', contextParts: {}};
+ var resid,
+ res = {},
+ parts,
+ pathParts = {affinity: 'common', contextKey: '*', contextParts: {}};
// views don't support our ".affinity." filename syntax.
pathParts.ext = libpath.extname(fullpath);
@@ -1710,15 +2342,20 @@ MojitoServer 0.1.0
pathParts.shortpath = libpath.basename(fullpath, pathParts.ext);
parts = pathParts.shortpath.split('.');
pathParts.viewEngine = parts.pop();
- pathParts.name = libpath.join(libpath.dirname(pathParts.relpath), parts.shift());
+ pathParts.name = libpath.join(libpath.dirname(pathParts.relpath),
+ parts.shift());
if (parts.length) {
pathParts.contextParts.device = parts.join('.');
pathParts.contextKey = libqs.stringify(pathParts.contextParts);
}
-
+ if (this._skipBadPath(pathParts)) {
+ return;
+ }
+
// Catch if the view has no engine i.e. "index.html" not "index.mu.html"
- if(!pathParts.name || pathParts.shortpath === pathParts.viewEngine){
- logger.log('Mojit view not loaded at "'+fullpath+'"', 'warn', NAME);
+ if (!pathParts.name || pathParts.shortpath === pathParts.viewEngine) {
+ logger.log('Mojit view not loaded at "' + fullpath + '"', 'warn',
+ NAME);
return;
}
@@ -1734,81 +2371,47 @@ MojitoServer 0.1.0
this._preloadSetDest(dest, resid, res, pathParts);
},
+
/*
- * TODO: Ric added this, needs review.
- *
* Note: this MUST be called before _preloadFileSpec()
+ *
+ * generates URL's about each spec in application.json
*/
- _urlsForAppSpecs: function(){
-
- var specs = this._appConfigNC.specs || {},
- appConfig, prefix, id, url;
-
- appConfig = this._appConfigNC.staticHandling || {};
+ _urlsForAppSpecs: function() {
+ var specs = this._appConfigStatic.specs || {},
+ appConfig,
+ prefix,
+ id,
+ url;
+
+ appConfig = this._appConfigStatic.staticHandling || {};
prefix = '/static';
- if(typeof appConfig.prefix !== 'undefined'){
- prefix = appConfig.prefix ? '/'+appConfig.prefix : '';
+ if (typeof appConfig.prefix !== 'undefined') {
+ prefix = appConfig.prefix ? '/' + appConfig.prefix : '';
}
-
- for(id in specs){
+
+ for (id in specs) {
if (specs.hasOwnProperty(id)) {
// Set the URL of the spec
- // TODO 2011-06-16: use appconfig.staticHandling.appName
- url = prefix+'/'+id+'/specs/default.json';
+ // TODO: use appconfig.staticHandling.appName
+ url = prefix + '/' + id + '/specs/default.json';
this._dynamicURLs[url] = 'application.json';
}
}
},
+
/*
- * TODO: Ric added this, needs review.
+ * prereads the configuration file, if possible
+ * (configuration files in YCB format cannot be preread)
*/
- _preloadFileSpec: function(dest, source, mojitType, fullpath, dir) {
-
- var name, url, config, appConfig, prefix;
-
- if (! this._libs.path.existsSync(fullpath)) {
- return;
- }
-
- appConfig = this._appConfigNC.staticHandling || {};
- prefix = '/static';
-
- if(typeof appConfig.prefix !== 'undefined'){
- prefix = appConfig.prefix ? '/'+appConfig.prefix : '';
- }
-
- // Set the URL of the spec
- url = prefix+'/'+mojitType+'/specs/'+libpath.basename(fullpath);
-
- // Set the name to the mojit type (this is for namespacing)
- name = mojitType;
-
- // If the filename is not "default" add it to the spec name
- if(libpath.basename(fullpath) !== 'default.json'){
- name+= ':'+libpath.basename(fullpath).split('.').slice(0,-1).join('.');
- }
-
- config = this._readConfigYCB({}, fullpath);
-
- // Just incase this has not been made
- if(!this._appConfigNC.specs){
- this._appConfigNC.specs = {};
- }
-
- // Add our spec to the specs map
- this._appConfigNC.specs[name] = config;
-
- // Add our spec to the Dynamic URLs list
- this._dynamicURLs[url] = fullpath;
- },
-
-
_prereadConfigs: function(src) {
- var ctxKey, res, resid;
+ var ctxKey,
+ res,
+ resid;
- if ((! src) || (! Object.keys(src))) {
+ if ((!src) || (!Object.keys(src))) {
return;
}
for (ctxKey in src.contexts) {
@@ -1816,9 +2419,12 @@ MojitoServer 0.1.0
for (resid in src[ctxKey]) {
if (src[ctxKey].hasOwnProperty(resid)) {
res = src[ctxKey][resid];
- if ('config' === res.type && !this._configCache[res.fsPath]) {
- //logger.log('prereading ' + res.fsPath, 'info', NAME);
- this._configCache[res.fsPath] = this._readConfigJSON(res.fsPath);
+ if ('config' === res.type &&
+ !this._jsonCache[res.fsPath]) {
+ //logger.log('prereading ' + res.fsPath, 'info',
+ // NAME);
+ this._jsonCache[res.fsPath] =
+ this._readConfigJSON(res.fsPath);
}
}
}
@@ -1827,52 +2433,174 @@ MojitoServer 0.1.0
},
+ /*
+ * reads and parses a JSON file
+ */
_readConfigJSON: function(fullpath) {
//logger.log('_readConfigJSON(' + fullpath + ')');
- var json, contents = this._libs.fs.readFileSync(fullpath, 'utf-8');
+ var json,
+ contents = this._libs.fs.readFileSync(fullpath, 'utf-8');
+
try {
json = JSON.parse(contents);
- }
- catch (e) {
- logger.log(this._reportJavaScriptSyntaxErrors(contents, fullpath), 'warn', NAME);
- throw new Error("Error parsing JSON file: " + fullpath);
+ } catch (e) {
+ logger.log(this._reportJavaScriptSyntaxErrors(contents, fullpath),
+ 'warn', NAME);
+ throw new Error('Error parsing JSON file: ' + fullpath);
}
return json;
},
- _readConfigYCB: function(ctx, fullpath) {
- var contents,
+ /*
+ * Create a lookup table for validating YCB dimensions and values. The
+ * table looks like this:
+ *
+ * <pre>
+ * {
+ * "dim1": {
+ * "val1": null,
+ * "val2": null,
+ * ...
+ * },
+ * ...
+ * }
+ * </pre>
+ *
+ * @param dimensions {object} Top-level YCB "dimensions" object
+ * @return object
+ */
+ _precalcValidYCBDimensions: function(dimensions) {
+ var validDims = {},
+ name,
+ i;
+
+ for (i = 0; i < dimensions.length; i += 1) {
+ for (name in dimensions[i]) {
+ if (dimensions[i].hasOwnProperty(name)) {
+ validDims[name] = {};
+ this._flattenYCBDimension(validDims[name],
+ dimensions[i][name]);
+ }
+ }
+ }
+
+ return validDims;
+ },
+
+
+ /*
+ * Flatten the keys in a nested structure into a single object. The first
+ * argument is modified. All values are set to null.
+ *
+ * @param keys {object} The accumulator for keys.
+ * @param obj {object}
+ */
+ _flattenYCBDimension: function(keys, obj) {
+ var name;
+
+ for (name in obj) {
+ if (obj.hasOwnProperty(name)) {
+ keys[name] = null;
+ if (typeof obj[name] === 'object') {
+ this._flattenYCBDimension(keys, obj[name]);
+ }
+ }
+ }
+ },
+
+
+ /*
+ * Return a context that contains only valid dimensions and values.
+ *
+ * @param ctx {object} runtime context
+ * @return {object} filtered runtime context
+ */
+ _getValidYCBContext: function(ctx) {
+ var validDims = this._validYCBDims,
+ validCtx = {},
+ name,
+ value;
+
+ for (name in ctx) {
+ if (ctx.hasOwnProperty(name)) {
+ value = ctx[name];
+ if (validDims[name] && validDims[name].hasOwnProperty(value)) {
+ validCtx[name] = value;
+ }
+ }
+ }
+ return validCtx;
+ },
+
+
+ /*
+ * reads a configuration file that is in YCB format
+ * @param ctx {object} runtime context
+ * @param fullpath {string} path to the YCB file
+ * @param isAppConfig {boolean} indicates whether the file being read is the application.json
+ * @return {object} the contextualized configuration
+ */
+ _readConfigYCB: function(ctx, fullpath, isAppConfig) {
+ var cacheKey,
+ appConfigStatic,
+ jsonCache = this._jsonCache,
+ json,
+ ycbCache = this._ycbCache,
ycb;
+
+ ctx = this._getValidYCBContext(ctx);
+
+ //cache key only needs to account for dynamic context
+ cacheKey = JSON.stringify(ctx);
+
//logger.log('_readConfigYCB('+fullpath+')', 'mojito', NAME);
- if (! fullpath) {
+ if (!fullpath) {
//logger.log('unknown fullpath', 'mojito', NAME);
return {};
}
- contents = this._configCache[fullpath];
- if (! contents) {
- contents = this._readConfigJSON(fullpath);
- this._configCache[fullpath] = contents;
+ if (!ycbCache[fullpath]) {
+ ycbCache[fullpath] = {};
}
- // TODO: bug #4942368, cache smarter so that context can be applied
- ycb = this._ycbCache[fullpath];
- if (! ycb) {
+ ycb = ycbCache[fullpath][cacheKey];
+ if (!ycb) {
+ json = jsonCache[fullpath];
+ if (!json) {
+ json = this._readConfigJSON(fullpath);
+ jsonCache[fullpath] = json;
+ }
+ json = this._ycbDims.concat(json);
+
+ ctx = this._mergeRecursive(this._cloneObj(this._defaultYCBContext),
+ ctx);
+
// libycb.read() will distructively modify its first argument
- ycb = libycb.read(this._cloneObj(contents), {} /*ctx*/);
- this._ycbCache[fullpath] = ycb;
+ ycb = libycb.read(this._cloneObj(json), ctx);
+ if (isAppConfig) {
+ appConfigStatic = this._cloneObj(this._appConfigStatic);
+ ycb = this._mergeRecursive(appConfigStatic, ycb);
+ }
+ ycbCache[fullpath][cacheKey] = ycb;
}
-
return ycb;
},
+ /*
+ * reads a configuration file for a mojit
+ * @param path {string} path to the file
+ * @param ycb {boolean} indicates whether the file should be read using the YCB library
+ * @return {object} the configuration
+ */
_readMojitConfigFile: function(path, ycb) {
//logger.log('_readMojitConfigFile(' + path + ',' + ycb + ')');
- var json, contents;
- if (! this._libs.path.existsSync(path)) {
+ var json,
+ contents;
+
+ if (!this._libs.path.existsSync(path)) {
return {};
}
if (ycb) {
@@ -1881,20 +2609,35 @@ MojitoServer 0.1.0
try {
contents = this._libs.fs.readFileSync(path, 'utf-8');
json = JSON.parse(contents);
- }
- catch (e) {
- logger.log(this._reportJavaScriptSyntaxErrors(contents, path), 'warn', NAME);
- throw new Error("Error reading or parsing JSON file: " + path);
+ } catch (e) {
+ logger.log(this._reportJavaScriptSyntaxErrors(contents, path),
+ 'warn', NAME);
+ throw new Error('Error reading or parsing JSON file: ' + path);
}
return json;
},
+ /*
+ * Sets up the static handling so that the package.json file for the
+ * mojit can be served. This is only done if the package.json file
+ * has defined yahoo.mojito.package or config.mojito.package to
+ * "public".
+ *
+ * @param pack {object} contents of the mojit's package.json file
+ * @param path {string} the mojit's directory
+ * @return {nothing} results are added to this object
+ */
_mojitPackageAsAsset: function(pack, path) {
- var pkg, packagePath, fakeResource;
+ var pkg,
+ packagePath,
+ fakeResource;
+
+ pkg = (pack.yahoo && pack.yahoo.mojito &&
+ pack.yahoo.mojito['package']) ||
+ (pack.config && pack.config.mojito &&
+ pack.config.mojito['package']);
- pkg = (pack.yahoo && pack.yahoo.mojito && pack.yahoo.mojito['package']) ||
- (pack.config && pack.config.mojito && pack.config.mojito['package']);
if (pkg === 'public') {
// We have to check if the "package.json" files wants to do this
packagePath = libpath.join(path, 'package.json');
@@ -1902,26 +2645,38 @@ MojitoServer 0.1.0
type: 'package',
fsPath: packagePath
};
- this._precalcStaticURL(fakeResource, (pack.name || libpath.basename(path)));
+ this._precalcStaticURL(fakeResource, (pack.name ||
+ libpath.basename(path)));
}
},
+ /*
+ * checks to see if the version of Mojito specified in a mojit's
+ * package.json matches the current verison of Mojito.
+ *
+ * @param pack {object} contents of the mojit's package.json file
+ * @param version {string} current version of mojito
+ * @return {boolean} returns true if the mojit can be used
+ */
_mojitoVersionMatch: function(pack, version) {
var packageVersion;
+
// There was no package.json file so assume version is OK
if (Object.keys(pack).length === 0) {
return true;
}
// If we have a version make sure it is less than the given one
- packageVersion = (pack.yahoo && pack.yahoo.mojito && pack.yahoo.mojito.version) ||
+ packageVersion = (pack.yahoo && pack.yahoo.mojito &&
+ pack.yahoo.mojito.version) ||
(pack.config && pack.config.mojito && pack.config.mojito.version);
+
if (packageVersion) {
if (packageVersion === '*') {
return true;
}
- // TODO: [bug 4647440] Put real version checking code here
+ // TODO: [Issue 95] Put real version checking code here.
return packageVersion <= version;
}
@@ -1930,13 +2685,42 @@ MojitoServer 0.1.0
},
+ /*
+ * takes the preloaded info and resolves ("cooks down") affinity, etc
+ *
+ * This function is a doozy. This is where all the magic happens as far
+ * as which version of each resource is used. The results are stored in
+ * this._fwMeta, _appMeta, _mojitMeta, etc. The primary key of these is
+ * the environment ("client" or "server"). The secondary key is the
+ * context key -- a string representation of the partial context.
+ *
+ * We do that to have fast lookup during runtime.
+ *
+ * The algorithm chooses first from the most specific source: mojit-
+ * specific has higher precedence than app-level, which has higher
+ * precedence than framework-provided. Within each of those, the
+ * environment-specific version ("client" or "server") has higher
+ * precedence than the "common" affinity.
+ *
+ * We do this for each context key (partial context). We resolve
+ * context inheritance (for example no-context versus device=iphone)
+ * at runtime (in getMojitTypeDetails()).
+ *
+ * (Half of the above algorithm is implemented here, and half in
+ * _cookdownMerge() which is a utility for this method.)
+ */
_cookdown: function() {
//logger.log('_cookdown()');
- var env, envs = [ 'client', 'server' ],
- type, i, merged,
- resid, resids, ctxKey;
+ var env,
+ envs = ['client', 'server'],
+ type, // mojit type
+ i,
+ merged, // results of _cookdownMerge()
+ resid,
+ resids, // array (really object keys) of all resource IDs
+ ctxKey;
- // _preload.mojitMeta is
+ // example of _preload.mojitMeta:
// [type][resid][affinity] = {
// "contexts": { "device=iphone": { "device":"iphone"} },
// "device=iphone": {
@@ -1944,14 +2728,16 @@ MojitoServer 0.1.0
// "fsPath": ...
// }
// }
- // resid was generated during _preloadFile*()
- // for example controller is 'controller'
- // model is 'model-docs'
+ // resid was generated during _preloadFile*(), for example:
+ // controller is 'controller'
+ // model is 'model-foo'
// view is 'view-index'
- // TODO DREW 2011-06-16: [bug 4647391] break down into smaller pieces (at least for more fine-grained unit testing)
+ //
+ // TODO 2011-06-16: [Issue 109] break down into smaller pieces
+ // (at least for more fine-grained unit testing).
for (type in this._preload.mojitMeta) {
if (this._preload.mojitMeta.hasOwnProperty(type)) {
- // TODO LINT 2011-06-16: [bug 4647450] detect dupe resids
+ // TODO: LINT [Issue 110] detect dupe resids.
resids = {};
// need resids from all sources
@@ -1973,32 +2759,43 @@ MojitoServer 0.1.0
for (resid in resids) {
if (resids.hasOwnProperty(resid)) {
- for (i=0; i<envs.length; i++) {
+ for (i = 0; i < envs.length; i += 1) {
env = envs[i];
merged = this._cookdownMerge(env, [
// ORDER IS IMPORTANT
- this._preload.fwMeta[resid], // source=fw
- this._preload.appMeta[resid], // source=app
- this._preload.mojitMeta[type][resid] // source=mojit
+ // source=fw -- lowest priority
+ this._preload.fwMeta[resid],
+ // source=app -- middle priority
+ this._preload.appMeta[resid],
+ // source=mojit -- highest priority
+ this._preload.mojitMeta[type][resid]
]);
if (merged) {
- if (! this._mojitMeta[env]) {
+ if (!this._mojitMeta[env]) {
this._mojitMeta[env] = {};
}
- if (! this._mojitMeta[env][type]) {
+ if (!this._mojitMeta[env][type]) {
this._mojitMeta[env][type] = {};
}
- if (! this._mojitMeta[env][type].contexts) {
+ if (!this._mojitMeta[env][type].contexts) {
this._mojitMeta[env][type].contexts = {};
}
for (ctxKey in merged.contexts) {
- if (merged.contexts.hasOwnProperty(ctxKey)) {
- this._mojitMeta[env][type].contexts[ctxKey] = merged.contexts[ctxKey];
- if (! this._mojitMeta[env][type][ctxKey]) {
- this._mojitMeta[env][type][ctxKey] = {};
+ if (merged.contexts.
+ hasOwnProperty(ctxKey)) {
+ this._mojitMeta[env][type
+ ].contexts[ctxKey] =
+ merged.contexts[ctxKey];
+ if (!this._mojitMeta[env
+ ][type][ctxKey]) {
+ this._mojitMeta[env
+ ][type][ctxKey] = {};
}
- // TODO DREW 2011-06-16: give example of datastructure
- this._mojitMeta[env][type][ctxKey][resid] = merged[ctxKey];
+ // TODO: give example of
+ // data structure.
+ this._mojitMeta[env
+ ][type][ctxKey][resid] =
+ merged[ctxKey];
}
}
} // have merged
@@ -2010,53 +2807,55 @@ MojitoServer 0.1.0
for (resid in this._preload.fwMeta) {
if (this._preload.fwMeta.hasOwnProperty(resid)) {
- for (i=0; i<envs.length; i++) {
+ for (i = 0; i < envs.length; i += 1) {
env = envs[i];
- merged = this._cookdownMerge(env, [
- this._preload.fwMeta[resid]
- ]);
+ merged = this._cookdownMerge(env,
+ [this._preload.fwMeta[resid]]);
if (merged) {
- if (! this._fwMeta[env]) {
+ if (!this._fwMeta[env]) {
this._fwMeta[env] = {};
}
- if (! this._fwMeta[env].contexts) {
+ if (!this._fwMeta[env].contexts) {
this._fwMeta[env].contexts = {};
}
for (ctxKey in merged.contexts) {
if (merged.contexts.hasOwnProperty(ctxKey)) {
- this._fwMeta[env].contexts[ctxKey] = merged.contexts[ctxKey];
- if (! this._fwMeta[env][ctxKey]) {
+ this._fwMeta[env].contexts[ctxKey] =
+ merged.contexts[ctxKey];
+ if (!this._fwMeta[env][ctxKey]) {
this._fwMeta[env][ctxKey] = {};
}
- this._fwMeta[env][ctxKey][resid] = merged[ctxKey];
+ this._fwMeta[env][ctxKey][resid] =
+ merged[ctxKey];
}
}
}
}
}
}
-
+
for (resid in this._preload.appMeta) {
if (this._preload.appMeta.hasOwnProperty(resid)) {
- for (i=0; i<envs.length; i++) {
+ for (i = 0; i < envs.length; i += 1) {
env = envs[i];
- merged = this._cookdownMerge(env, [
- this._preload.appMeta[resid]
- ]);
+ merged = this._cookdownMerge(env,
+ [this._preload.appMeta[resid]]);
if (merged) {
- if (! this._appMeta[env]) {
+ if (!this._appMeta[env]) {
this._appMeta[env] = {};
}
- if (! this._appMeta[env].contexts) {
+ if (!this._appMeta[env].contexts) {
this._appMeta[env].contexts = {};
}
for (ctxKey in merged.contexts) {
if (merged.contexts.hasOwnProperty(ctxKey)) {
- this._appMeta[env].contexts[ctxKey] = merged.contexts[ctxKey];
- if (! this._appMeta[env][ctxKey]) {
+ this._appMeta[env].contexts[ctxKey] =
+ merged.contexts[ctxKey];
+ if (!this._appMeta[env][ctxKey]) {
this._appMeta[env][ctxKey] = {};
}
- this._appMeta[env][ctxKey][resid] = merged[ctxKey];
+ this._appMeta[env][ctxKey][resid] =
+ merged[ctxKey];
}
}
}
@@ -2067,18 +2866,87 @@ MojitoServer 0.1.0
},
- _precalcYuiDependencies: function() {
- var e, i, env, envs = [ 'client', 'server' ],
- mojitType, ctxKey, module,
- parts, required, resid, res,
- sorted, ctxs, onlyLoadNecessary = true;
+ /*
+ * This is a utility for _cookdown(). See docs on that for details.
+ *
+ * The general idea is that we start with the lowest priority items
+ * and let higher priority items clobber.
+ *
+ * @param env {string} "client" or "server"
+ * @param srcs {array} ORDER MATTERS! list of resources to merge
+ * @return {DOING} DOING
+ */
+ _cookdownMerge: function(env, srcs) {
+ var merged = { contexts: {} },
+ affinities = ['common', env], // priority order
+ s,
+ src,
+ lastS = srcs.length - 1,
+ found = false, // TODO: when is found==false?
+ a,
+ affinity,
+ ctx,
+ res,
+ ctxKey;
+
+ for (s = 0; s < srcs.length; s += 1) {
+ src = srcs[s];
+ if (!src) {
+ continue;
+ }
+ for (a = 0; a < affinities.length; a += 1) {
+ affinity = affinities[a];
+ if (!src[affinity]) {
+ continue;
+ }
+ for (ctxKey in src[affinity].contexts) {
+ if (src[affinity].contexts.hasOwnProperty(ctxKey)) {
+ merged.contexts[ctxKey] =
+ src[affinity].contexts[ctxKey];
+ res = this._cloneObj(src[affinity][ctxKey]);
+ if (('config' === res.type) && (s !== lastS)) {
+ // only pull in configs from the last source
+ continue;
+ }
+ merged.type = res.type;
+ merged[ctxKey] = res;
+ found = true;
+ }
+ }
+ }
+ }
+ if (!found) {
+ return null;
+ }
+ return merged;
+ },
+
- for (e=0; e<envs.length; e++) {
+ /*
+ * calculates, at server start time, the YUI module dependencies
+ * for mojit controllers and binders
+ */
+ _precalcYuiDependencies: function() {
+ var e,
+ i,
+ env,
+ envs = ['client', 'server'],
+ mojitType,
+ ctxKey,
+ module,
+ parts,
+ required,
+ resid,
+ res,
+ sorted,
+ ctxs;
+
+ for (e = 0; e < envs.length; e += 1) {
env = envs[e];
// mojit-specific
// --------------
- if (! this._mojitYuiSorted[env]) {
+ if (!this._mojitYuiSorted[env]) {
this._mojitYuiRequired[env] = {};
this._mojitYuiSorted[env] = {};
this._mojitYuiSortedPaths[env] = {};
@@ -2086,124 +2954,194 @@ MojitoServer 0.1.0
for (mojitType in this._mojitMeta[env]) {
if (this._mojitMeta[env].hasOwnProperty(mojitType)) {
- if (! this._mojitYuiSorted[env][mojitType]) {
+ if (!this._mojitYuiSorted[env][mojitType]) {
this._mojitYuiRequired[env][mojitType] = {};
this._mojitYuiSorted[env][mojitType] = {};
this._mojitYuiSortedPaths[env][mojitType] = {};
}
for (ctxKey in this._mojitMeta[env][mojitType].contexts) {
- if (this._mojitMeta[env][mojitType].contexts.hasOwnProperty(ctxKey)) {
+ if (this._mojitMeta[env
+ ][mojitType].contexts.hasOwnProperty(ctxKey)) {
+
+ // we handle non-context version below
if ('*' === ctxKey) {
continue;
}
- if (! this._mojitYuiSorted[env][mojitType].contexts) {
- this._mojitYuiRequired[env][mojitType].contexts = {};
- this._mojitYuiSorted[env][mojitType].contexts = {};
- this._mojitYuiSortedPaths[env][mojitType].contexts = {};
+ if (!this._mojitYuiSorted[env
+ ][mojitType].contexts) {
+ this._mojitYuiRequired[env
+ ][mojitType].contexts = {};
+ this._mojitYuiSorted[env
+ ][mojitType].contexts = {};
+ this._mojitYuiSortedPaths[env
+ ][mojitType].contexts = {};
}
parts = {};
- this._precalcYuiDependencies_getDepParts(env, this._mojitMeta[env][mojitType]['*'], parts);
- this._precalcYuiDependencies_getDepParts(env, this._mojitMeta[env][mojitType][ctxKey], parts);
- if (parts.controller && parts.modules['inlinecss/'+mojitType]) {
- parts.modules[parts.controller.yuiModuleName].requires.push('inlinecss/'+mojitType);
+ this._precalcYuiDependencies_getDepParts(env,
+ this._mojitMeta[env][mojitType]['*'], parts);
+ this._precalcYuiDependencies_getDepParts(env,
+ this._mojitMeta[env][mojitType][ctxKey], parts);
+ if (parts.controller &&
+ parts.modules['inlinecss/' + mojitType]) {
+ parts.modules[parts.controller.yuiModuleName].
+ requires.push('inlinecss/' + mojitType);
}
- this._mojitYuiSorted[env][mojitType].contexts[ctxKey] =
- this._mojitMeta[env][mojitType].contexts[ctxKey];
- this._mojitYuiRequired[env][mojitType].contexts[ctxKey] =
- this._mojitMeta[env][mojitType].contexts[ctxKey];
+ this._mojitYuiSorted[env
+ ][mojitType].contexts[ctxKey] =
+ this._mojitMeta[env
+ ][mojitType].contexts[ctxKey];
+ this._mojitYuiRequired[env
+ ][mojitType].contexts[ctxKey] =
+ this._mojitMeta[env
+ ][mojitType].contexts[ctxKey];
if (parts.controller) {
- parts.required[parts.controller.yuiModuleName] = true;
+ parts.required[parts.controller.yuiModuleName] =
+ true;
// dependencies necessary to dispatch the mojit
parts.required['mojito-dispatcher'] = true;
sorted = this._sortYUIModules(
- this._mojitMeta[env][mojitType].contexts[ctxKey],
- env, this._appConfigNC.yui, mojitType, parts.modules, parts.required);
- this._mojitYuiRequired[env][mojitType][ctxKey] = Object.keys(parts.required);
- this._mojitYuiSorted[env][mojitType][ctxKey] = sorted.sorted;
- this._mojitYuiSortedPaths[env][mojitType][ctxKey] = sorted.paths;
+ this._mojitMeta[env][mojitType
+ ].contexts[ctxKey],
+ env,
+ this._appConfigStatic.yui,
+ mojitType,
+ parts.modules,
+ parts.required
+ );
+ this._mojitYuiRequired[env
+ ][mojitType][ctxKey] =
+ Object.keys(parts.required);
+ this._mojitYuiSorted[env
+ ][mojitType][ctxKey] = sorted.sorted;
+ this._mojitYuiSortedPaths[env
+ ][mojitType][ctxKey] = sorted.paths;
}
// also calculate sortedPaths for each individual binder
if ('client' === env) {
- for (resid in this._mojitMeta[env][mojitType][ctxKey]) {
- if (this._mojitMeta[env][mojitType][ctxKey].hasOwnProperty(resid)) {
- res = this._mojitMeta[env][mojitType][ctxKey][resid];
+ for (resid in this._mojitMeta[env
+ ][mojitType][ctxKey]) {
+ if (this._mojitMeta[env
+ ][mojitType][ctxKey
+ ].hasOwnProperty(resid)) {
+ res = this._mojitMeta[env
+ ][mojitType][ctxKey][resid];
if (res.type !== 'binder') {
continue;
}
required = {};
required[res.yuiModuleName] = true;
- // all binders have this dependency, even if not explicitly given
+ // all binders have this dependency,
+ // even if not explicitly given
required['mojito-client'] = true;
- // view engines are needed to support mojitProxy.render()
+ // view engines are needed to support
+ // mojitProxy.render()
for (i in parts.viewEngines) {
- if (parts.viewEngines.hasOwnProperty(i)) {
- required[parts.viewEngines[i]] = true;
+ if (parts.viewEngines.
+ hasOwnProperty(i)) {
+ required[parts.viewEngines[i]] =
+ true;
}
}
sorted = this._sortYUIModules(
- this._mojitMeta[env][mojitType].contexts[ctxKey],
- env, this._appConfigNC.yui, mojitType, parts.modules, required);
+ this._mojitMeta[env][mojitType
+ ].contexts[ctxKey],
+ env,
+ this._appConfigStatic.yui,
+ mojitType,
+ parts.modules,
+ required
+ );
res.yuiSortedPaths = sorted.paths;
}
}
- }
- } // foreach context (except '*')
- }
+ } // env==client
+ }
+ } // foreach context (except '*')
+ // here's where we handle the non-context version
if (this._mojitMeta[env][mojitType]['*']) {
- if (! this._mojitYuiSorted[env][mojitType].contexts) {
- this._mojitYuiRequired[env][mojitType].contexts = {};
+ if (!this._mojitYuiSorted[env][mojitType].contexts) {
+ this._mojitYuiRequired[env
+ ][mojitType].contexts = {};
this._mojitYuiSorted[env][mojitType].contexts = {};
- this._mojitYuiSortedPaths[env][mojitType].contexts = {};
+ this._mojitYuiSortedPaths[env
+ ][mojitType].contexts = {};
}
parts = {};
- this._precalcYuiDependencies_getDepParts(env, this._mojitMeta[env][mojitType]['*'], parts);
- if (parts.controller && parts.modules['inlinecss/'+mojitType]) {
- parts.modules[parts.controller.yuiModuleName].requires.push('inlinecss/'+mojitType);
+ this._precalcYuiDependencies_getDepParts(env,
+ this._mojitMeta[env][mojitType]['*'],
+ parts);
+ if (parts.controller && parts.modules['inlinecss/' +
+ mojitType]) {
+ parts.modules[parts.controller.yuiModuleName].
+ requires.push('inlinecss/' + mojitType);
}
this._mojitYuiSorted[env][mojitType].contexts['*'] =
- this._mojitMeta[env][mojitType].contexts['*'];
+ this._mojitMeta[env][mojitType].contexts['*'];
this._mojitYuiRequired[env][mojitType].contexts['*'] =
- this._mojitMeta[env][mojitType].contexts['*'];
+ this._mojitMeta[env][mojitType].contexts['*'];
if (parts.controller) {
- parts.required[parts.controller.yuiModuleName] = true;
+ parts.required[parts.controller.yuiModuleName] =
+ true;
// dependencies necessary to dispatch the mojit
parts.required['mojito-dispatcher'] = true;
sorted = this._sortYUIModules(
this._mojitMeta[env][mojitType].contexts['*'],
- env, this._appConfigNC.yui, mojitType, parts.modules, parts.required);
- this._mojitYuiRequired[env][mojitType]['*'] = Object.keys(parts.required);
- this._mojitYuiSorted[env][mojitType]['*'] = sorted.sorted;
- this._mojitYuiSortedPaths[env][mojitType]['*'] = sorted.paths;
+ env,
+ this._appConfigStatic.yui,
+ mojitType,
+ parts.modules,
+ parts.required
+ );
+ this._mojitYuiRequired[env][mojitType]['*'] =
+ Object.keys(parts.required);
+ this._mojitYuiSorted[env][mojitType]['*'] =
+ sorted.sorted;
+ this._mojitYuiSortedPaths[env][mojitType]['*'] =
+ sorted.paths;
}
// also calculate sortedPaths for each individual binder
if ('client' === env) {
- for (resid in this._mojitMeta[env][mojitType]['*']) {
- if (this._mojitMeta[env][mojitType]['*'].hasOwnProperty(resid)) {
- res = this._mojitMeta[env][mojitType]['*'][resid];
+ for (resid in this._mojitMeta[env
+ ][mojitType]['*']) {
+ if (this._mojitMeta[env][mojitType
+ ]['*'].hasOwnProperty(resid)) {
+ res = this._mojitMeta[env][mojitType
+ ]['*'][resid];
if (res.type !== 'binder') {
continue;
}
required = {};
required[res.yuiModuleName] = true;
- // all binders have this dependency, even if not explicitly given
+ // all binders have this dependency, even if
+ // not explicitly given
required['mojito-client'] = true;
- // view engines are needed to support mojitProxy.render()
+ // view engines are needed to support
+ // mojitProxy.render()
for (i in parts.viewEngines) {
- if (parts.viewEngines.hasOwnProperty(i)) {
- required[parts.viewEngines[i]] = true;
+ if (parts.viewEngines.
+ hasOwnProperty(i)
+ ) {
+ required[parts.viewEngines[i]] =
+ true;
}
}
sorted = this._sortYUIModules(
- this._mojitMeta[env][mojitType].contexts['*'],
- env, this._appConfigNC.yui, mojitType, parts.modules, required);
+ this._mojitMeta[env][mojitType
+ ].contexts['*'],
+ env,
+ this._appConfigStatic.yui,
+ mojitType,
+ parts.modules,
+ required
+ );
res.yuiSortedPaths = sorted.paths;
}
}
- }
+ } // env==client
} // context=='*'
}
} // foreach mojitType
@@ -2218,11 +3156,20 @@ MojitoServer 0.1.0
// requires: list of required modules
// }
// .moduleSources hash yuiModuleName:source
- // .required hash yuiModuleName:true of modules required by the source controller
+ // .required hash yuiModuleName:true of modules required by the
+ // source controller
// .viewEngines hash name:yuiModuleName of view engines
+ //
+ // @param env {string} "client" or "server"
+ // @param source {object} list of resources
+ // @param dest {object} where to add results
+ // @return {nothing} results put in "dest" argument
_precalcYuiDependencies_getDepParts: function(env, source, dest) {
- var resid, res, viewEngine;
- if (! source) {
+ var resid,
+ res,
+ viewEngine;
+
+ if (!source) {
return;
}
dest.required = dest.required || {};
@@ -2230,7 +3177,8 @@ MojitoServer 0.1.0
dest.modules = dest.modules || {};
dest.moduleSources = dest.moduleSources || {};
- // all mojits essentially have this dependency implicitly (even it not given explicitly)
+ // all mojits essentially have this dependency implicitly (even it not
+ // given explicitly)
dest.required.mojito = true;
for (resid in source) {
@@ -2240,7 +3188,8 @@ MojitoServer 0.1.0
viewEngine = source['addon-view-engines-' + res.viewEngine];
if (viewEngine) {
dest.required[viewEngine.yuiModuleName] = true;
- dest.viewEngines[res.viewEngine] = viewEngine.yuiModuleName;
+ dest.viewEngines[res.viewEngine] =
+ viewEngine.yuiModuleName;
}
}
if (res.yuiModuleName) {
@@ -2254,92 +3203,177 @@ MojitoServer 0.1.0
}
dest.modules[res.yuiModuleName] = {
requires: res.yuiModuleMeta.requires,
- fullpath: (('client' === env) ? res.staticHandlerURL : res.fsPath)
+ fullpath: (('client' === env) ?
+ res.staticHandlerURL :
+ res.fsPath)
};
dest.moduleSources[res.yuiModuleName] = res.source;
- if (('controller' === res.type) ) {
+ if (('controller' === res.type)) {
dest.controller = res;
}
}
}
}
- // TODO 2011-08-10 -- move other dynamic dependency adjustments (compiled views inlinecss) here
+ // TODO: move other dynamic dependency adjustments
+ // (compiled views inlinecss) here.
+ },
+
+
+ /*
+ * uses YUI Loader to sort a list of YUI modules
+ */
+ _sortYUIModules: function(ctx, env, yuiConfig, mojitType, modules,
+ required) {
+ var YUI = serverYUI,
+ Y,
+ loader,
+ sortedPaths = {},
+ usePrecomputed = -1 !== this._appConfigStatic.yui.
+ dependencyCalculations.indexOf('precomputed'),
+ useOnDemand = -1 !== this._appConfigStatic.yui.
+ dependencyCalculations.indexOf('ondemand'),
+ j,
+ module,
+ info;
+
+ if (!usePrecomputed) {
+ useOnDemand = true;
+ }
+
+ if ('client' === env) {
+ // Use clientYUI to avoid cross-contamination with serverYUI
+ YUI = clientYUI;
+ // GlobalConfig is needed on nodejs but is invalid on the client
+ delete YUI.GlobalConfig;
+ }
+
+ // We don't actually need the full list, just the base required modules.
+ // YUI.Loader() will do the rest at runtime.
+ if (useOnDemand) {
+ for (module in required) {
+ if (required.hasOwnProperty(module)) {
+ sortedPaths[module] = modules[module].fullpath;
+ }
+ }
+ return { sorted: Object.keys(required), paths: sortedPaths };
+ }
+
+ Y = YUI().useSync('loader-base');
+ loader = new Y.Loader({ lang: ctx.lang });
+
+ // We need to clear YUI's cached dependencies, since there's no
+ // guarantee that the previously calculated dependencies have been done
+ // using the same context as this calculation.
+ delete YUI.Env._renderedMods;
+
+ // This approach seems odd, but it's what the YUI Configurator is also
+ // doing.
+ if (yuiConfig && yuiConfig.base) {
+ loader.base = yuiConfig.base;
+ } else {
+ loader.base = Y.Env.meta.base + Y.Env.meta.root;
+ }
+ loader.addGroup({modules: modules}, mojitType);
+ loader.calculate({required: required});
+ for (j = 0; j < loader.sorted.length; j += 1) {
+ module = loader.sorted[j];
+ info = loader.moduleInfo[module];
+ if (info) {
+ sortedPaths[module] = info.fullpath || loader._url(info.path);
+ }
+ }
+ return { sorted: loader.sorted, paths: sortedPaths };
},
+ /*
+ * calculates the static handling URL for a resource
+ *
+ * @param res {object} metadata about the resource
+ * @param mojitType {string} mojit type, can be undefined for non-mojit-specific resources
+ * @return {nothing} new metadata added to the "res" argument
+ */
_precalcStaticURL: function(res, mojitType) {
/* alternate approach which shows power of precalculating the URL and
* then passing it around everywhere
- res.staticHandlerURL = '/static/' + Math.floor(Math.random() * 1000000000);
+ res.staticHandlerURL = '/static/' + Math.floor(Math.random() *
+ 1000000000);
return;
*/
- var url, parts = [],
- path, i, relpath,
- config = this._appConfigNC.staticHandling || {},
+ var url,
+ parts = [],
+ path,
+ i,
+ relpath,
+ config = this._appConfigStatic.staticHandling || {},
prefix = config.prefix,
appName = config.appName || this._shortRoot,
frameworkName = config.frameworkName || 'mojito',
- rollupParts = [], rollupFsPath;
- // TODO DREW: [bug 4647457] magic constants should should come from fw.json
+ rollupParts = [],
+ rollupFsPath;
+
+ // TODO: [Issue 111] magic constants should should come from fw.json.
/*
- Server only framework mojits like DaliProxy and HTMLFrameMojit should never have static
- URLs associated with them, so we skip them. This never used to be an issue until we added
- the "assumeRollups" functionality to preload JSON specs for specified mojits during the
- compile step (mojito compile json) for Livestand. I think we need to reevaluate this entire
- process so we don't have such a fragile condition below. -- Matt
+ Server only framework mojits like DaliProxy and HTMLFrameMojit should
+ never have static URLs associated with them, so we skip them. This never
+ used to be an issue until we added the "assumeRollups" functionality to
+ preload JSON specs for specified mojits during the compile step (mojito
+ compile json) for Livestand. I think we need to reevaluate this entire
+ process so we don't have such a fragile condition below.
*/
- // TODO: reevaluate this entire process so we don't have such a fragile condition below.
+ // TODO: reevaluate this entire process so we don't have such a fragile
+ // condition below.
if (mojitType === 'DaliProxy' || mojitType === 'HTMLFrameMojit') {
return;
}
switch (res.type) {
- case 'action':
- path = libpath.join('actions', libpath.basename(res.fsPath));
- break;
- case 'addon':
- i = res.fsPath.indexOf('/addons/');
- relpath = res.fsPath.substr(i+8);
- path = libpath.join('addons', relpath);
- break;
- case 'asset':
- i = res.fsPath.indexOf('/assets/');
- relpath = res.fsPath.substr(i+8);
- path = libpath.join('assets', relpath);
- break;
- case 'binder':
- i = res.fsPath.indexOf('/binders/');
- relpath = res.fsPath.substr(i+9);
- path = libpath.join('binders', relpath);
- break;
- case 'controller':
- path = libpath.basename(res.fsPath);
- break;
- case 'model':
- path = libpath.join('models', libpath.basename(res.fsPath));
- break;
- case 'view':
- i = res.fsPath.indexOf('/views/');
- relpath = res.fsPath.substr(i+7);
- path = libpath.join('views', relpath);
- break;
- case 'yui-lang':
- path = libpath.join('lang', libpath.basename(res.fsPath));
- break;
- case 'yui-module':
- i = res.fsPath.indexOf('/autoload/');
- relpath = res.fsPath.substr(i+10);
- path = libpath.join('autoload', relpath);
- break;
- case 'package':
- path = libpath.basename(res.fsPath);
- break;
- default:
- return;
+ case 'action':
+ path = libpath.join('actions', libpath.basename(res.fsPath));
+ break;
+ case 'addon':
+ i = res.fsPath.indexOf('/addons/');
+ relpath = res.fsPath.substr(i + 8);
+ path = libpath.join('addons', relpath);
+ break;
+ case 'asset':
+ i = res.fsPath.indexOf('/assets/');
+ relpath = res.fsPath.substr(i + 8);
+ path = libpath.join('assets', relpath);
+ break;
+ case 'binder':
+ i = res.fsPath.indexOf('/binders/');
+ relpath = res.fsPath.substr(i + 9);
+ path = libpath.join('binders', relpath);
+ break;
+ case 'controller':
+ path = libpath.basename(res.fsPath);
+ break;
+ case 'model':
+ path = libpath.join('models', libpath.basename(res.fsPath));
+ break;
+ case 'view':
+ i = res.fsPath.indexOf('/views/');
+ relpath = res.fsPath.substr(i + 7);
+ path = libpath.join('views', relpath);
+ break;
+ case 'yui-lang':
+ path = libpath.join('lang', libpath.basename(res.fsPath));
+ break;
+ case 'yui-module':
+ i = res.fsPath.indexOf('/autoload/');
+ relpath = res.fsPath.substr(i + 10);
+ path = libpath.join('autoload', relpath);
+ break;
+ case 'package':
+ path = libpath.basename(res.fsPath);
+ break;
+ default:
+ return;
}
- if (! config.hasOwnProperty('prefix')) {
+ if (!config.hasOwnProperty('prefix')) {
prefix = 'static';
}
if (prefix) {
@@ -2354,35 +3388,37 @@ MojitoServer 0.1.0
rollupParts.push(appName);
rollupFsPath = libpath.join(this._root, 'rollup.client.js');
}
- }
- else if ('app' === res.source) {
+ } else if ('app' === res.source) {
parts.push(appName);
if (config.useRollups && res.yuiModuleName) {
rollupParts.push(appName);
rollupFsPath = libpath.join(this._root, 'rollup.client.js');
}
- }
- else {
+ } else {
parts.push(mojitType);
if (config.useRollups && res.yuiModuleName) {
rollupParts.push(mojitType);
- rollupFsPath = libpath.join(this._mojitPaths[mojitType], 'rollup.client.js');
+ rollupFsPath = libpath.join(this._mojitPaths[mojitType],
+ 'rollup.client.js');
}
}
if (mojitType) {
- if (! this._mojitAssetRoots[mojitType]) {
- this._mojitAssetRoots[mojitType] = '/' + libpath.join(parts.join('/'), 'assets');
+ if (!this._mojitAssetRoots[mojitType]) {
+ this._mojitAssetRoots[mojitType] = '/' +
+ libpath.join(parts.join('/'), 'assets');
}
}
// only use rollup URL if rollup file exists or we are assuming rollups
- if ((rollupFsPath && this._libs.path.existsSync(rollupFsPath) && config.useRollups) || this._appConfigNC.assumeRollups) {
+ if ((rollupFsPath && this._libs.path.existsSync(rollupFsPath) &&
+ config.useRollups) || this._appConfigStatic.assumeRollups) {
// useful for debugging: path += '?orig=' + path;
- res.rollupURL = '/' + libpath.join(rollupParts.join('/'), 'rollup.client.js');
+ res.rollupURL = '/' + libpath.join(rollupParts.join('/'),
+ 'rollup.client.js');
url = res.rollupURL;
this._staticURLs[url] = rollupFsPath;
}
- if (! url) {
+ if (!url) {
url = '/' + libpath.join(parts.join('/'), path);
this._staticURLs[url] = res.fsPath;
}
@@ -2390,7 +3426,12 @@ MojitoServer 0.1.0
},
- // attempt to gather YUI-module details
+ /*
+ * attempt to gather YUI-module details
+ *
+ * @param res {object} metadata about the resource
+ * @return {nothing} new metadata added to the "res" argument
+ */
_precalcYuiModule: function(res) {
var file = this._libs.fs.readFileSync(res.fsPath, 'utf8'),
ctx = {
@@ -2400,27 +3441,29 @@ MojitoServer 0.1.0
window: {},
document: {},
YUI: {
- add: function(name,fn,version,meta) {
+ add: function(name, fn, version, meta) {
res.yuiModuleName = name;
res.yuiModuleVersion = version;
res.yuiModuleMeta = meta || {};
}
}
};
+
try {
libvm.runInNewContext(file, ctx, res.fsPath);
- }
- catch (e) {
+ } catch (e) {
if (e.stack.indexOf('SyntaxError:') === 0) {
// the stack of a SyntaxError thrown by runInNewContext() lacks
- // filename, line and column numbers for the error. Also, it does
- // not write to stderr as the docs claim.
+ // filename, line and column numbers for the error. Also, it
+ // does not write to stderr as the docs claim.
// see "Lack of error message in vm.* stuff" from 2011-04-29 at
- // http://groups.google.com/group/nodejs/browse_thread/thread/2075b964a3f7dd79/bd0df1ae36829813
+ // http://groups.google.com/group/nodejs/browse_thread/
+ // thread/2075b964a3f7dd79/bd0df1ae36829813
logger.log(this._reportJavaScriptSyntaxErrors(file));
- logger.log(e.message + ' in file: ' + res.fsPath, 'error', NAME);
+ logger.log(e.message + ' in file: ' + res.fsPath, 'error',
+ NAME);
} else {
logger.log(e.message + '\n' + e.stack, 'error', NAME);
@@ -2430,71 +3473,50 @@ MojitoServer 0.1.0
},
- _getMojitConfig: function(env, ctx, mojitIn, name) {
- //logger.log('_getMojitConfig('+env+','+mojitIn+','+name+')');
- var resid, res, mojit,
- appConfig, specs, spec;
-
- mojit = mojitIn;
- resid = 'config-' + name;
+ /*
+ * reads one the configuration files for a mojit
+ *
+ * @param env {string} "client" or "server"
+ * @param ctx {object} runtime context
+ * @param mojitType {string} name of mojit
+ * @param name {string} config resource name, either "definition" or "defaults"
+ */
+ _getMojitConfig: function(env, ctx, mojitType, name) {
+ //logger.log('_getMojitConfig('+env+','+mojitType+','+name+')');
+ var resid,
+ res;
- if (! this._mojitMeta[env][mojit]) {
- throw new Error("Cannot find meta data for mojit type \"" + mojit + "\"");
+ if (!this._mojitMeta[env][mojitType]) {
+ throw new Error('Cannot find meta data for mojit type \"' + mojitType +
+ '\"');
}
- res = this._getContextualizedResource(this._mojitMeta[env][mojit], ctx, resid);
- if (! res) {
+ resid = 'config-' + name;
+ res = this._getContextualizedResource(this._mojitMeta[env][mojitType], ctx,
+ resid);
+ if (!res) {
return {};
}
return this._readConfigYCB(ctx, res.fsPath);
},
- _sortYUIModules: function(ctx, env, yuiConfig, mojitType, modules, required) {
- var Y, loader, sortedPaths = {}, j, module, info;
- Y = YUI().useSync('loader-base');
- loader = new Y.Loader({ lang: ctx.lang });
-
- // We need to clear YUI's cached dependencies, since there's no guarantee that
- // the previously calculated dependencies have been done using the same context
- // as this calculation.
- delete YUI.Env._renderedMods;
-
- // This approach seems odd, but it's what the YUI Configurator is also doing.
- if (yuiConfig && yuiConfig.base) {
- loader.base = yuiConfig.base;
- }
- else {
- loader.base = Y.Env.meta.base + Y.Env.meta.root;
- }
- loader.addGroup({modules: modules}, mojitType);
- loader.calculate({required: required});
- // This is required because loader.calculate() isn't taking into consideration
- // conditional modules.
- // http://yuilibrary.com/projects/yui3/ticket/2530343
- if ('server' === env) {
- loader.sorted.unshift('io-nodejs');
- loader.sorted.unshift('nodejs-node');
- loader.sorted.unshift('nodejs-dom');
- }
- for (j=0; j<loader.sorted.length; j++) {
- module = loader.sorted[j];
- info = loader.moduleInfo[module];
- if (info) {
- sortedPaths[module] = info.fullpath || loader._url(info.path);
- }
- }
- return { sorted: loader.sorted, paths: sortedPaths };
- },
-
-
+ /*
+ * returns whether a runtime context matches a partial context
+ *
+ * @param ctx {object} runtime context
+ * @param ctxParts {object} partial context
+ */
_matchContext: function(ctx, ctxParts) {
var k;
+
for (k in ctxParts) {
if (ctxParts.hasOwnProperty(k)) {
- // FUTURE -- handle "lang" slightly specially ("en" should match "en-US")
- // For now we will skip the "lang" check as is could change on the fly in the client
- // and we need all the "lang" files avaliable in our YUI instance.
+ // FUTURE -- handle "lang" slightly specially ("en" should match
+ // "en-US")
+ // For now we will skip the "lang" check as it could change on
+ // the fly in the client and we need all the "lang" files
+ // available in our YUI instance.
if (k !== 'lang' && ctx[k] !== ctxParts[k]) {
return false;
}
@@ -2504,13 +3526,22 @@ MojitoServer 0.1.0
},
+ /*
+ * returns a list of resource metadata that match the context
+ *
+ * @param src {object} list of contextualized resources, key is contextKey
+ * @param ctx {object} context to match
+ * @return {object} list of resources, key is resource ID
+ */
_getResourceListForContext: function(src, ctx) {
- var list = {}, // key: value
- key, ctxKey;
+ var list = {}, // resid: resource
+ resid,
+ ctxKey;
+
if (src['*']) {
- for (key in src['*']) {
- if (src['*'].hasOwnProperty(key)) {
- list[key] = src['*'][key];
+ for (resid in src['*']) {
+ if (src['*'].hasOwnProperty(resid)) {
+ list[resid] = src['*'][resid];
}
}
}
@@ -2519,12 +3550,12 @@ MojitoServer 0.1.0
if ('*' === ctxKey) {
continue;
}
- if (! this._matchContext(ctx, src.contexts[ctxKey])) {
+ if (!this._matchContext(ctx, src.contexts[ctxKey])) {
continue;
}
- for (key in src[ctxKey]) {
- if (src[ctxKey].hasOwnProperty(key)) {
- list[key] = src[ctxKey][key];
+ for (resid in src[ctxKey]) {
+ if (src[ctxKey].hasOwnProperty(resid)) {
+ list[resid] = src[ctxKey][resid];
}
}
}
@@ -2533,9 +3564,20 @@ MojitoServer 0.1.0
},
+ /*
+ * returns a list of the language resources
+ * doesn't discriminate based on context: returns all langs for all
+ * contexts.
+ *
+ * @param src {object} list of contextualized resources, key is contextKey
+ * @return {object} list of language resources, key is resource ID
+ */
_getLangList: function(src) {
var list = {}, // resid: res
- ctxKey, resid, res;
+ ctxKey,
+ resid,
+ res;
+
for (ctxKey in src.contexts) {
if (src.contexts.hasOwnProperty(ctxKey)) {
for (resid in src[ctxKey]) {
@@ -2552,51 +3594,20 @@ MojitoServer 0.1.0
},
- // TODO DREW 2011-06-16: [bug 4647391] more comments, example, etc. get knuthy
- // this is an "affinity resolver"
- _cookdownMerge: function(env, srcs) {
- var merged = {contexts: {}},
- affinities = [ 'common', env ],
- s, src, lastS = srcs.length - 1,
- found = false, // TODO DREW 2011-06-16: when is found==false?
- a, affinity, ctx, res, ctxKey;
- for (s=0; s<srcs.length; s++) {
- src = srcs[s];
- if (! src) {
- continue;
- }
- for (a=0; a<affinities.length; a++) {
- affinity = affinities[a];
- if (! src[affinity]) {
- continue;
- }
- for (ctxKey in src[affinity].contexts) {
- if (src[affinity].contexts.hasOwnProperty(ctxKey)) {
- merged.contexts[ctxKey] = src[affinity].contexts[ctxKey];
- res = this._cloneObj(src[affinity][ctxKey]);
- if (('config' === res.type) && (s !== lastS)) {
- // only pull in configs from the last source
- continue;
- }
- merged.type = res.type;
- merged[ctxKey] = res;
- found = true;
- }
- }
- }
- }
- if (! found) {
- return null;
- }
- return merged;
- },
-
-
+ /*
+ * returns the metadata for a resource specific for a particular runtime context
+ *
+ * @param src {object} list of contextualized resources, key is contextKey
+ * @param ctx {object} context to match
+ * @param resid {string} ID of resource to find
+ * @return {object} resource metadata
+ */
_getContextualizedResource: function(src, ctx, resid) {
- var ctxKey, res;
+ var ctxKey,
+ res;
- // TODO: [bug 4647333] Review, Ric added this for when there is no app.json file
- if(!src || !src.contexts){
+ // TODO: [Issue 100] Review, for when there is no app.json file.
+ if (!src || !src.contexts) {
return {};
}
@@ -2606,7 +3617,7 @@ MojitoServer 0.1.0
if ('*' === ctxKey) {
continue;
}
- if (! this._matchContext(ctx, src.contexts[ctxKey])) {
+ if (!this._matchContext(ctx, src.contexts[ctxKey])) {
continue;
}
res = src[ctxKey][resid];
@@ -2620,16 +3631,25 @@ MojitoServer 0.1.0
},
+ /*
+ * utility for the _preloadFile*() methods that sets the new resource metadata
+ * into a location consistent with the other parts of the algorithm
+ *
+ * @param dest {object} where to store the results
+ * @param resid {string} resource ID
+ * @param res {object} metadata about the resource
+ * @param pathParts {object} results of _parsePath(), or the equivalent
+ */
_preloadSetDest: function(dest, resid, res, pathParts) {
- if (! dest[resid]) {
+ if (!dest[resid]) {
dest[resid] = {};
}
dest = dest[resid];
- if (! dest[pathParts.affinity]) {
+ if (!dest[pathParts.affinity]) {
dest[pathParts.affinity] = {};
}
dest = dest[pathParts.affinity];
- if (! dest.contexts) {
+ if (!dest.contexts) {
dest.contexts = {};
}
dest.contexts[pathParts.contextKey] = pathParts.contextParts;
@@ -2637,22 +3657,36 @@ MojitoServer 0.1.0
},
- // TODO DREW 2011-06-16: [bug 4647391] comment this stuff
+ /*
+ * utility for the _preloadFile*() methods that takes a file path
+ * and returns metadata about it
+ *
+ * @param fullpath {string} full path to resource
+ * @param defaultAffinity {string} affinity to use if the resource filename doesn't specify one
+ * @param dir {string} base directory of resource type
+ */
_parsePath: function(fullpath, defaultAffinity, dir) {
- var shortpath, reldir, parts, outParts = [],
- i, part, device, affinity,
+ var shortpath,
+ reldir,
+ parts,
+ outParts = [],
+ i,
+ part,
+ device,
+ affinity,
out = {
contextKey: '*',
contextParts: {},
affinity: defaultAffinity || 'server'
};
- if (! dir) {
+
+ if (!dir) {
dir = libpath.dirname(fullpath);
}
dir = libpath.join(dir, '/');
out.relpath = fullpath.substr(dir.length);
- // TODO -- support strict {name}.{selector}?.{affinity}?.{ext} syntax
+ // TODO: support strict {name}.{selector}?.{affinity}?.{ext} syntax.
out.ext = libpath.extname(fullpath);
shortpath = libpath.basename(out.relpath, out.ext);
reldir = libpath.dirname(out.relpath);
@@ -2661,14 +3695,15 @@ MojitoServer 0.1.0
if (affinity) {
out.affinity = affinity;
}
- device = this._detectDeviceFromShortFilename(shortpath, out.contextParts.device);
+ device = this._detectDeviceFromShortFilename(shortpath,
+ out.contextParts.device);
if (device) {
out.contextParts.device = device;
}
outParts.push(this._extractRootNameFromShortFilename(shortpath));
- if (! this._objectIsEmpty(out.contextParts)) {
+ if (!this._objectIsEmpty(out.contextParts)) {
out.contextKey = libqs.stringify(out.contextParts);
}
@@ -2679,51 +3714,95 @@ MojitoServer 0.1.0
},
+ /*
+ * indicates whether file should be skipped based on its path
+ *
+ * @param pathParts {object} return value of _parsePath() (or the equivalent)
+ * @return {boolean} true indicates that the file should be skipped
+ */
+ _skipBadPath: function(pathParts) {
+ var ext = pathParts.ext.substring(1);
+ if (ext.match(isNotAlphaNum)) {
+ return true;
+ }
+ return false;
+ },
+
+
/*
* Generate a report of syntax errors for JavaScript code. This is also
* very useful to find syntax errors in JSON documents.
*
* @param {string} js the JavaScript
- * @param {string} filename OPTIONAL. the name of the file containing the JavaScript
+ * @param {string} filename OPTIONAL. the name of the file containing the
+ * JavaScript
* @return {string} if errors were found, a multi-line error report
*/
_reportJavaScriptSyntaxErrors: function(js, filename) {
// use a really lenient JSLINT to find syntax errors
- var JSLINT = require('./management/fulljslint').JSLINT,
+ var jslint = require('./management/fulljslint').jslint,
opts = {
// turn off all the usual checks
- devel: true, browser: true, node: true, rhino: true, widget: true, windows: true,
- bitwise: true, regexp: true, confusion: true, undef: true, 'continue': true,
- unparam: true, debug: true, sloppy: true, eqeq: true, sub: true, es5: true,
- vars: true, evil: true, white: true, forin: true, css: true, newcap: true,
- cap: true, nomen: true, on: true, plusplus: true, fragment: true,
+ devel: true,
+ browser: true,
+ node: true,
+ rhino: true,
+ widget: true,
+ windows: true,
+ bitwise: true,
+ regexp: true,
+ confusion: true,
+ undef: true,
+ 'continue': true,
+ unparam: true,
+ debug: true,
+ sloppy: true,
+ eqeq: true,
+ sub: true,
+ es5: true,
+ vars: true,
+ evil: true,
+ white: true,
+ forin: true,
+ css: true,
+ newcap: true,
+ cap: true,
+ nomen: true,
+ on: true,
+ plusplus: true,
+ fragment: true,
// prevent well-known globals from showing up as errors
- predef:[
- "exports", // CommonJS
- "YUI", "YUI_config", "YAHOO", "YAHOO_config", "Y", // YUI
- "global", "process","require", "__filename", "module", // Node
- "document", "navigator", "console", "self", "window" // Browser
+ predef: [
+ // CommonJS
+ 'exports',
+ // YUI
+ 'YUI', 'YUI_config', 'YAHOO', 'YAHOO_config', 'Y',
+ // Node
+ 'global', 'process', 'require', '__filename', 'module',
+ // Browser
+ 'document', 'navigator', 'console', 'self', 'window'
]
},
// identify errors about undefined globals
- nameIsNotDefined = / is not defined.$/,
+ nameIsNotDefined = / is not defined\.$/,
success,
report = [],
len,
e,
i;
- success = JSLINT(js, opts);
+ success = jslint(js, opts);
if (!success) {
- len = JSLINT.errors.length;
- for (i = 0; i < len; i++) {
- e = JSLINT.errors[i];
+ len = jslint.errors.length;
+ for (i = 0; i < len; i += 1) {
+ e = jslint.errors[i];
if (e && e.reason && !nameIsNotDefined.test(e.reason)) {
report.push(e.line + ',' + e.character + ': ' + e.reason);
- report.push(' ' + (e.evidence || '').replace(/^\s+|\s+$/, ''));
+ report.push(' ' +
+ (e.evidence || '').replace(/^\s+|\s+$/, ''));
}
}
}
@@ -2736,23 +3815,45 @@ MojitoServer 0.1.0
},
+ /*
+ * finds the affinity in the filename
+ *
+ * @param name {string} filename
+ * @return {string|undefined} affinity found in the filename
+ */
_detectAffinityFromShortFilename: function(name) {
var affinity;
+
if (name.indexOf('.') >= 0) {
affinity = new Affinity(name.split('.').pop());
}
return affinity;
},
+
+ /*
+ * finds the device in the filename
+ *
+ * @param name {string} filename
+ * @return {string|undefined} device found in the filename
+ */
_detectDeviceFromShortFilename: function(name) {
- // FUTURE: [bug 4647471]real device detection
+ // FUTURE: [Issue 86]real device detection
var device;
+
if (name.indexOf('iphone') > -1) {
device = 'iphone';
}
return device;
},
+
+ /*
+ * returns the selector for the runtime context
+ *
+ * @param ctx {object} runtime context
+ * @return {string|null} selector for context
+ */
_selectorFromContext: function(ctx) {
if (ctx.device) {
return ctx.device;
@@ -2760,8 +3861,16 @@ MojitoServer 0.1.0
return null;
},
+
+ /*
+ * returns the short filename without the selector
+ *
+ * @param name {string} short filename
+ * @return {string} short filename without the selector
+ */
_extractRootNameFromShortFilename: function(name) {
var parts;
+
if (name.indexOf('.') === -1) {
return name;
}
@@ -2770,34 +3879,45 @@ MojitoServer 0.1.0
return parts.join('.');
},
+
+ // returns true if the object is empty
_objectIsEmpty: function(o) {
- if (! o) {
+ if (!o) {
return true;
}
return (0 === Object.keys(o).length);
},
- // from http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically/383245#383245
+ // from http://stackoverflow.com/questions/171251/
+ // how-can-i-merge-properties-of-two-javascript-objects-dynamically/
+ // 383245#383245
/*
- * Recursively merge properties of two objects
- */
+ * Recursively merge one object onto another
+ *
+ * @param dest {object} object to merge into
+ * @param src {object} object to merge onto "dest"
+ * @param matchType {boolean} controls whether a non-object in the src is
+ * allowed to clobber a non-object in the dest (if a different type)
+ * @return {object} the modified "dest" object is also returned directly
+ */
_mergeRecursive: function(dest, src, typeMatch) {
var p;
+
for (p in src) {
if (src.hasOwnProperty(p)) {
// Property in destination object set; update its value.
- if ( src[p] && src[p].constructor === Object ) {
- if(!dest[p]){
+ if (src[p] && src[p].constructor === Object) {
+ if (!dest[p]) {
dest[p] = {};
}
dest[p] = this._mergeRecursive(dest[p], src[p]);
} else {
- if(dest[p] && typeMatch){
- if(typeof dest[p] === typeof src[p]){
+ if (dest[p] && typeMatch) {
+ if (typeof dest[p] === typeof src[p]) {
dest[p] = src[p];
}
- }else{
+ } else {
dest[p] = src[p];
}
}
@@ -2807,35 +3927,52 @@ MojitoServer 0.1.0
},
+ // deep copies an object
_cloneObj: function(o) {
- var newO, i;
+ var newO,
+ i;
- if(typeof(o) !== 'object') {
+ if (typeof o !== 'object') {
return o;
}
- if(!o) {
+ if (!o) {
return o;
}
if ('[object Array]' === Object.prototype.toString.apply(o)) {
newO = [];
- for(i=0; i<o.length; i++) {
+ for (i = 0; i < o.length; i += 1) {
newO[i] = this._cloneObj(o[i]);
}
return newO;
}
newO = {};
- for(i in o) {
+ for (i in o) {
if (o.hasOwnProperty(i)) {
newO[i] = this._cloneObj(o[i]);
}
}
return newO;
+ },
+
+
+ /*
+ * A wrapper for fs.readdirSync() that guarantees ordering. The order in
+ * which the file system is walked is significant within the resource
+ * store, e.g., when looking up a matching context.
+ *
+ * @param path {string} directory to read
+ * @return {array} files in the directory
+ */
+ _sortedReaddirSync: function(path) {
+ var out = this._libs.fs.readdirSync(path);
+ return out.sort();
}
};
+
module.exports = ServerStore;