Applicaton now obeys a mount-point set by middleware.
authorDavid Schoonover <dsc@wikimedia.org>
Tue, 17 Jul 2012 23:08:24 +0000 (16:08 -0700)
committerDavid Schoonover <dsc@wikimedia.org>
Tue, 17 Jul 2012 23:40:43 +0000 (16:40 -0700)
58 files changed:
Cokefile
README.md
lib/base/base-model.js
lib/base/base-model.mod.js
lib/base/base.js
lib/base/base.mod.js
lib/base/index.js
lib/base/index.mod.js
lib/base/parser-mixin.js [new file with mode: 0644]
lib/base/parser-mixin.mod.js [new file with mode: 0644]
lib/chart/chart-type.js
lib/chart/chart-type.mod.js
lib/chart/option/chart-option-model.js
lib/chart/option/chart-option-model.mod.js
lib/chart/type/d3-chart.js
lib/chart/type/d3-chart.mod.js
lib/chart/type/dygraphs.js
lib/chart/type/dygraphs.mod.js
lib/client.js [new file with mode: 0644]
lib/client.mod.js [new file with mode: 0644]
lib/data/datasource-model.js
lib/data/datasource-model.mod.js
lib/graph/graph-model.js
lib/graph/graph-model.mod.js
lib/limn.js
lib/limn.mod.js
lib/server/middleware.js
lib/server/server.js
lib/util/index.js
lib/util/index.mod.js
lib/util/parser.js
lib/util/parser.mod.js
src/base/base-model.co
src/base/base.co
src/base/index.co
src/base/parser-mixin.co [new file with mode: 0644]
src/chart/chart-type.co
src/chart/option/chart-option-model.co
src/chart/type/d3-chart.co
src/chart/type/dygraphs.co
src/client.co [moved from src/limn.co with 89% similarity]
src/data/datasource-model.co
src/graph/graph-model.co
src/server/index.js
src/server/middleware.co
src/server/server.co
src/util/index.co
src/util/parser.co
src/version.js
www/css/hicons.css
www/css/hicons.styl
www/css/layout.css
www/css/layout.styl
www/footer.jade [new file with mode: 0644]
www/geo.jade
www/layout.jade
www/mixins/helpers.jade
www/modules.yaml

index c19c035..4f39c17 100644 (file)
--- a/Cokefile
+++ b/Cokefile
@@ -35,9 +35,7 @@ task \setup 'Ensure project is set up for development.' ->
 
 
 task \server 'Start dev server' ->
-    invoke \setup
-    say ''
-    run 'src/server/server.co'
+    run './src/server/server.co'
 
 
 task \build 'Build coco sources' ->
index befe80d..2bfa087 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,60 +2,3 @@
 
 
 
-### Setting Up
-
-This is a [node.js][nodejs] project, so you need node > 0.6.x and the node package manager, [npm][npm].
-
-Once that's done, I recommend you install Coco globally so you don't have to futz with your path: `npm install -g coco` -- if you choose not to, you should probably add `./node_modules/.bin` to `PATH` in your bashrc or something.
-
-Next, install your source checkout in dev mode by running `npm link` from the project root. This will also download and install all the package dependencies.
-
-Finally, start the `server` task using Coke (it's like Rake for Coco) with `coke server`. While this does what it says on the tin, it also seems to have a habit of randomly losing parts of stderr. For now, you can work around this by manually starting the server: `coke link && lib/server/server.co`
-
-You should now have a server running on 8081.
-
-### Project Layout
-- assets/                 - static images
-- data/                   - json, yaml, csv, etc. files that contain graph configuration and graph content data
-- data/datasources/       - graph content data
-- data/graphs/            - saved graph configurations.
-- lib/                    - [Coco][coco] files.  Application logic lives here.
-- lib/{chart,dashboard,dataset,graph}/ - Models and View Classes
-- lib/template/           - client side [Jade][jade] views.  These are included and rendered by View classes.
-- lib/server/             - Server side [Coco][coco] files.  
-- lib/server/server.co    - [Express][expressjs] server setup.   Routing is done here.
-- lib/server/controllers/ - Server side controllers.  Routed to by [express-resource][].
-- www/                    - (Mostly) static [Jade][jade] HTML templates and [Stylus][stylus] CSS templates.  The [Jade][jade] templates are rendered by the server side controllers in lib/server/controllers/.
-- var/                    - Compiled JavaScript and CSS files.
-
-### Deployment
-Coco needs to be compiled down to JavaScript in order for it to be executed.  In development environments, this is done on the fly.  In production environments, all Coco is compiled down into JavaScript files and placed in a dist/ directory.  These JavaScript (and compiled Stylus CSS files) are served up directly to the browser upon request, rather than having to be compiled first.
-
-deploy.sh currently builds a distribution tmp/dist, and then rsyncs this over to reportcard.wmflabs.org.  You will need an account with sudo permissions on reportcard2.pmtpa.wmflabs in order to deploy.
-
-
-### Notes
-
-- This project is written in [Coco][coco], a dialect of [CoffeeScript][coffee] -- they both compile 
-  down to JavaScript. The pair are very, very similar, [except][coco-improvements] 
-  for [a few][coco-incompatibilities] [things][coco-vs-coffee]. If you can read JavaScript and Ruby, 
-  you can understand Coco and CoffeeScript. (I refer to the [CoffeeScript docs][coffee-docs] for 
-  the syntax, and I find the [comparison page][coco-vs-coffee] to be the best reference for Coco.)
-  
-- Coco require compilation before it'll run in the browser (though node can run it directly -- `#!/usr/bin/env coco` will work as a shebang as well). I've written [request middleware][connect-compiler] that recompiles stale files on demand, and it is pretty cool.
-  
-
-
-[nodejs]: http://nodejs.org/
-[npm]: http://npmjs.org/
-[coco]: https://github.com/satyr/coco
-[coco-vs-coffee]: https://github.com/satyr/coco/wiki/side-by-side-comparison
-[coco-improvements]: https://github.com/satyr/coco/wiki/improvements
-[coco-incompatibilities]: https://github.com/satyr/coco/wiki/incompatibilities
-[coffee]: http://coffeescript.org/
-[coffee-docs]: http://coffeescript.org/#language
-[connect-compiler]: https://github.com/dsc/connect-compiler
-[jade]: https://github.com/visionmedia/jade
-[expressjs]: http://expressjs.com/guide.html
-[express-resource]: https://github.com/visionmedia/express-resource
-[stylus]: http://learnboost.github.com/stylus/
\ No newline at end of file
index 2e3394c..7fe3c06 100644 (file)
@@ -1,5 +1,6 @@
-var Backbone, op, BaseBackboneMixin, mixinBase, BaseModel, BaseList, _ref, _, __slice = [].slice;
+var Backbone, limn, op, BaseBackboneMixin, mixinBase, BaseModel, BaseList, _ref, _, __slice = [].slice;
 Backbone = require('backbone');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 _ref = require('./base-mixin'), BaseBackboneMixin = _ref.BaseBackboneMixin, mixinBase = _ref.mixinBase;
 /**
@@ -17,7 +18,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend(mixinBase({
     return BaseModel;
   }()),
   url: function(){
-    return this.urlRoot + "/" + this.get('id') + ".json";
+    return limn.mount(this.urlRoot) + "/" + this.get('id') + ".json";
   },
   has: function(key){
     return this.get(key) != null;
@@ -221,12 +222,13 @@ BaseList = exports.BaseList = Backbone.Collection.extend(mixinBase({
     });
   },
   url: function(){
-    var id;
+    var root, id;
+    root = limn.mount(this.urlRoot);
     id = this.get('id') || this.get('slug');
     if (id) {
-      return this.urlRoot + "/" + id + ".json";
+      return root + "/" + id + ".json";
     } else {
-      return this.urlRoot + ".json";
+      return root + ".json";
     }
   },
   load: function(){
index 9d7f227..421525a 100644 (file)
@@ -1,7 +1,8 @@
 require.define('/node_modules/limn/base/base-model.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var Backbone, op, BaseBackboneMixin, mixinBase, BaseModel, BaseList, _ref, _, __slice = [].slice;
+var Backbone, limn, op, BaseBackboneMixin, mixinBase, BaseModel, BaseList, _ref, _, __slice = [].slice;
 Backbone = require('backbone');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 _ref = require('./base-mixin'), BaseBackboneMixin = _ref.BaseBackboneMixin, mixinBase = _ref.mixinBase;
 /**
@@ -19,7 +20,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend(mixinBase({
     return BaseModel;
   }()),
   url: function(){
-    return this.urlRoot + "/" + this.get('id') + ".json";
+    return limn.mount(this.urlRoot) + "/" + this.get('id') + ".json";
   },
   has: function(key){
     return this.get(key) != null;
@@ -223,12 +224,13 @@ BaseList = exports.BaseList = Backbone.Collection.extend(mixinBase({
     });
   },
   url: function(){
-    var id;
+    var root, id;
+    root = limn.mount(this.urlRoot);
     id = this.get('id') || this.get('slug');
     if (id) {
-      return this.urlRoot + "/" + id + ".json";
+      return root + "/" + id + ".json";
     } else {
-      return this.urlRoot + ".json";
+      return root + ".json";
     }
   },
   load: function(){
index eac2083..701c9cc 100644 (file)
@@ -18,11 +18,14 @@ Base = (function(superclass){
   Base.displayName = 'Base';
   var prototype = __extend(Base, superclass).prototype, constructor = Base;
   function Base(){
+    var _ref;
     this.__class__ = this.constructor;
     this.__superclass__ = this.constructor.superclass;
     this.__apply_bind__();
     superclass.call(this);
-    this.__class__.emit('new', this);
+    if (typeof (_ref = this.__class__).emit == 'function') {
+      _ref.emit('new', this);
+    }
   }
   /**
    * A list of method-names to bind on `initialize`; set this on a subclass to override.
@@ -46,10 +49,10 @@ Base = (function(superclass){
     return this.getClassName() + "()";
   };
   Base.extended = function(Subclass){
-    var k, v, _own = {}.hasOwnProperty;
-    for (k in this) if (_own.call(this, k)) {
+    var k, v;
+    for (k in this) {
       v = this[k];
-      if (typeof v === 'function') {
+      if (typeof v === 'function' && !_.contains(['apply', 'call', 'constructor', 'toString'], k)) {
         Subclass[k] = v;
       }
     }
index d8daec5..77c5599 100644 (file)
@@ -20,11 +20,14 @@ Base = (function(superclass){
   Base.displayName = 'Base';
   var prototype = __extend(Base, superclass).prototype, constructor = Base;
   function Base(){
+    var _ref;
     this.__class__ = this.constructor;
     this.__superclass__ = this.constructor.superclass;
     this.__apply_bind__();
     superclass.call(this);
-    this.__class__.emit('new', this);
+    if (typeof (_ref = this.__class__).emit == 'function') {
+      _ref.emit('new', this);
+    }
   }
   /**
    * A list of method-names to bind on `initialize`; set this on a subclass to override.
@@ -48,10 +51,10 @@ Base = (function(superclass){
     return this.getClassName() + "()";
   };
   Base.extended = function(Subclass){
-    var k, v, _own = {}.hasOwnProperty;
-    for (k in this) if (_own.call(this, k)) {
+    var k, v;
+    for (k in this) {
       v = this[k];
-      if (typeof v === 'function') {
+      if (typeof v === 'function' && !_.contains(['apply', 'call', 'constructor', 'toString'], k)) {
         Subclass[k] = v;
       }
     }
index 5caef36..4334860 100644 (file)
@@ -1,4 +1,4 @@
-var mixins, models, views, cache, cascading, data_binding;
+var mixins, models, views, cache, cascading, data_binding, parser_mixin;
 exports.Base = require('./base');
 mixins = require('./base-mixin');
 models = require('./base-model');
@@ -7,6 +7,8 @@ cache = require('./model-cache');
 cascading = require('./cascading-model');
 data_binding = require('./data-binding');
 __import(__import(__import(__import(__import(__import(exports, mixins), models), views), cache), cascading), data_binding);
+parser_mixin = require('./parser-mixin');
+__import(exports, parser_mixin);
 function __import(obj, src){
   var own = {}.hasOwnProperty;
   for (var key in src) if (own.call(src, key)) obj[key] = src[key];
index d23b254..f7867c9 100644 (file)
@@ -1,6 +1,6 @@
 require.define('/node_modules/limn/base/index.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var mixins, models, views, cache, cascading, data_binding;
+var mixins, models, views, cache, cascading, data_binding, parser_mixin;
 exports.Base = require('./base');
 mixins = require('./base-mixin');
 models = require('./base-model');
@@ -9,6 +9,8 @@ cache = require('./model-cache');
 cascading = require('./cascading-model');
 data_binding = require('./data-binding');
 __import(__import(__import(__import(__import(__import(exports, mixins), models), views), cache), cascading), data_binding);
+parser_mixin = require('./parser-mixin');
+__import(exports, parser_mixin);
 function __import(obj, src){
   var own = {}.hasOwnProperty;
   for (var key in src) if (own.call(src, key)) obj[key] = src[key];
diff --git a/lib/base/parser-mixin.js b/lib/base/parser-mixin.js
new file mode 100644 (file)
index 0000000..8a67ce4
--- /dev/null
@@ -0,0 +1,100 @@
+var Parsers, BaseModel, BaseList, BaseView, Mixin, ParserMixin, ParsingModel, ParsingList, ParsingView, _ref;
+Parsers = require('../util/parser').Parsers;
+_ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, BaseView = _ref.BaseView, Mixin = _ref.Mixin;
+exports.Parsers = Parsers;
+/**
+ * @class Methods for a class to select parsers by type reflection.
+ * @mixin
+ */
+exports.ParserMixin = ParserMixin = (function(superclass){
+  ParserMixin.displayName = 'ParserMixin';
+  var prototype = __extend(ParserMixin, superclass).prototype, constructor = ParserMixin;
+  __import(ParserMixin.prototype, Parsers);
+  function ParserMixin(target){
+    return Mixin.call(ParserMixin, target);
+  }
+  prototype.parseValue = function(v, type){
+    return this.getParser(type)(v);
+  };
+  prototype.getParser = function(type){
+    var fn, t, _i, _ref, _len;
+    type == null && (type = 'String');
+    fn = this["parse" + type];
+    if (typeof fn === 'function') {
+      return fn;
+    }
+    type = _(String(type).toLowerCase());
+    for (_i = 0, _len = (_ref = ['Integer', 'Float', 'Number', 'Boolean', 'Object', 'Array', 'Function']).length; _i < _len; ++_i) {
+      t = _ref[_i];
+      if (type.startsWith(t.toLowerCase())) {
+        return this["parse" + t];
+      }
+    }
+    return this.defaultParser || this.parseString;
+  };
+  prototype.getParserFromExample = function(v){
+    var type;
+    if (v == null) {
+      return null;
+    }
+    type = typeof v;
+    if (type !== 'object') {
+      return this.getParser(type);
+    } else if (_.isArray(v)) {
+      return this.getParser('Array');
+    } else {
+      return this.getParser('Object');
+    }
+  };
+  return ParserMixin;
+}(Mixin));
+/**
+ * @class Basic model which mixes in the ParserMixin.
+ * @extends BaseModel
+ * @borrows ParserMixin
+ */
+ParsingModel = exports.ParsingModel = BaseModel.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingModel(){
+      return BaseModel.apply(this, arguments);
+    }
+    return ParsingModel;
+  }())
+}));
+/**
+ * @class Basic collection which mixes in the ParserMixin.
+ * @extends BaseList
+ * @borrows ParserMixin
+ */
+ParsingList = exports.ParsingList = BaseList.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingList(){
+      return BaseList.apply(this, arguments);
+    }
+    return ParsingList;
+  }())
+}));
+/**
+ * @class Basic view which mixes in the ParserMixin.
+ * @extends BaseView
+ * @borrows ParserMixin
+ */
+ParsingView = exports.ParsingView = BaseView.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingView(){
+      return BaseView.apply(this, arguments);
+    }
+    return ParsingView;
+  }())
+}));
+function __extend(sub, sup){
+  function fun(){} fun.prototype = (sub.superclass = sup).prototype;
+  (sub.prototype = new fun).constructor = sub;
+  if (typeof sup.extended == 'function') sup.extended(sub);
+  return sub;
+}
+function __import(obj, src){
+  var own = {}.hasOwnProperty;
+  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
+  return obj;
+}
\ No newline at end of file
diff --git a/lib/base/parser-mixin.mod.js b/lib/base/parser-mixin.mod.js
new file mode 100644 (file)
index 0000000..510e807
--- /dev/null
@@ -0,0 +1,104 @@
+require.define('/node_modules/limn/base/parser-mixin.js', function(require, module, exports, __dirname, __filename, undefined){
+
+var Parsers, BaseModel, BaseList, BaseView, Mixin, ParserMixin, ParsingModel, ParsingList, ParsingView, _ref;
+Parsers = require('../util/parser').Parsers;
+_ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, BaseView = _ref.BaseView, Mixin = _ref.Mixin;
+exports.Parsers = Parsers;
+/**
+ * @class Methods for a class to select parsers by type reflection.
+ * @mixin
+ */
+exports.ParserMixin = ParserMixin = (function(superclass){
+  ParserMixin.displayName = 'ParserMixin';
+  var prototype = __extend(ParserMixin, superclass).prototype, constructor = ParserMixin;
+  __import(ParserMixin.prototype, Parsers);
+  function ParserMixin(target){
+    return Mixin.call(ParserMixin, target);
+  }
+  prototype.parseValue = function(v, type){
+    return this.getParser(type)(v);
+  };
+  prototype.getParser = function(type){
+    var fn, t, _i, _ref, _len;
+    type == null && (type = 'String');
+    fn = this["parse" + type];
+    if (typeof fn === 'function') {
+      return fn;
+    }
+    type = _(String(type).toLowerCase());
+    for (_i = 0, _len = (_ref = ['Integer', 'Float', 'Number', 'Boolean', 'Object', 'Array', 'Function']).length; _i < _len; ++_i) {
+      t = _ref[_i];
+      if (type.startsWith(t.toLowerCase())) {
+        return this["parse" + t];
+      }
+    }
+    return this.defaultParser || this.parseString;
+  };
+  prototype.getParserFromExample = function(v){
+    var type;
+    if (v == null) {
+      return null;
+    }
+    type = typeof v;
+    if (type !== 'object') {
+      return this.getParser(type);
+    } else if (_.isArray(v)) {
+      return this.getParser('Array');
+    } else {
+      return this.getParser('Object');
+    }
+  };
+  return ParserMixin;
+}(Mixin));
+/**
+ * @class Basic model which mixes in the ParserMixin.
+ * @extends BaseModel
+ * @borrows ParserMixin
+ */
+ParsingModel = exports.ParsingModel = BaseModel.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingModel(){
+      return BaseModel.apply(this, arguments);
+    }
+    return ParsingModel;
+  }())
+}));
+/**
+ * @class Basic collection which mixes in the ParserMixin.
+ * @extends BaseList
+ * @borrows ParserMixin
+ */
+ParsingList = exports.ParsingList = BaseList.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingList(){
+      return BaseList.apply(this, arguments);
+    }
+    return ParsingList;
+  }())
+}));
+/**
+ * @class Basic view which mixes in the ParserMixin.
+ * @extends BaseView
+ * @borrows ParserMixin
+ */
+ParsingView = exports.ParsingView = BaseView.extend(ParserMixin.mix({
+  constructor: (function(){
+    function ParsingView(){
+      return BaseView.apply(this, arguments);
+    }
+    return ParsingView;
+  }())
+}));
+function __extend(sub, sup){
+  function fun(){} fun.prototype = (sub.superclass = sup).prototype;
+  (sub.prototype = new fun).constructor = sub;
+  if (typeof sup.extended == 'function') sup.extended(sub);
+  return sub;
+}
+function __import(obj, src){
+  var own = {}.hasOwnProperty;
+  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
+  return obj;
+}
+
+});
index 721046d..c7aad90 100644 (file)
@@ -1,9 +1,10 @@
-var moment, Backbone, op, ReadyEmitter, Parsers, ParserMixin, KNOWN_CHART_TYPES, ChartType, _ref, _, __slice = [].slice;
+var moment, Backbone, limn, op, ReadyEmitter, Parsers, ParserMixin, KNOWN_CHART_TYPES, ChartType, _ref, _, __slice = [].slice;
 moment = require('moment');
 Backbone = require('backbone');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 ReadyEmitter = require('../util/event').ReadyEmitter;
-_ref = require('../util/parser'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin;
+_ref = require('../base'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin;
 /**
  * Map of known libraries by name.
  * @type Object
@@ -140,13 +141,14 @@ exports.ChartType = ChartType = (function(superclass){
    * info about valid options, along with their types and defaults.
    */
   prototype.loadSpec = function(){
-    var proto, _this = this;
+    var proto, url, _this = this;
     if (this.ready) {
       return this;
     }
     proto = this.constructor.prototype;
+    url = (limn.config.mount !== '/' ? limn.config.mount : '') + this.SPEC_URL;
     jQuery.ajax({
-      url: this.SPEC_URL,
+      url: url,
       dataType: 'json',
       success: function(spec){
         proto.spec = spec;
index e62af90..6362824 100644 (file)
@@ -1,11 +1,12 @@
 require.define('/node_modules/limn/chart/chart-type.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var moment, Backbone, op, ReadyEmitter, Parsers, ParserMixin, KNOWN_CHART_TYPES, ChartType, _ref, _, __slice = [].slice;
+var moment, Backbone, limn, op, ReadyEmitter, Parsers, ParserMixin, KNOWN_CHART_TYPES, ChartType, _ref, _, __slice = [].slice;
 moment = require('moment');
 Backbone = require('backbone');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 ReadyEmitter = require('../util/event').ReadyEmitter;
-_ref = require('../util/parser'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin;
+_ref = require('../base'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin;
 /**
  * Map of known libraries by name.
  * @type Object
@@ -142,13 +143,14 @@ exports.ChartType = ChartType = (function(superclass){
    * info about valid options, along with their types and defaults.
    */
   prototype.loadSpec = function(){
-    var proto, _this = this;
+    var proto, url, _this = this;
     if (this.ready) {
       return this;
     }
     proto = this.constructor.prototype;
+    url = (limn.config.mount !== '/' ? limn.config.mount : '') + this.SPEC_URL;
     jQuery.ajax({
-      url: this.SPEC_URL,
+      url: url,
       dataType: 'json',
       success: function(spec){
         proto.spec = spec;
index f066113..b6b58c0 100644 (file)
@@ -1,7 +1,6 @@
-var op, Parsers, ParserMixin, ParsingModel, ParsingView, BaseModel, BaseList, TagSet, KNOWN_TAGS, ChartOption, ChartOptionList, _ref, _, __slice = [].slice;
+var op, BaseModel, BaseList, Parsers, ParserMixin, ParsingModel, ParsingView, TagSet, KNOWN_TAGS, ChartOption, ChartOptionList, _ref, _, __slice = [].slice;
 _ref = require('../../util'), _ = _ref._, op = _ref.op;
-_ref = require('../../util/parser'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin, ParsingModel = _ref.ParsingModel, ParsingView = _ref.ParsingView;
-_ref = require('../../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
+_ref = require('../../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin, ParsingModel = _ref.ParsingModel, ParsingView = _ref.ParsingView;
 /**
  * @class A set of tags.
  */
index 0e5a921..5214160 100644 (file)
@@ -1,9 +1,8 @@
 require.define('/node_modules/limn/chart/option/chart-option-model.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var op, Parsers, ParserMixin, ParsingModel, ParsingView, BaseModel, BaseList, TagSet, KNOWN_TAGS, ChartOption, ChartOptionList, _ref, _, __slice = [].slice;
+var op, BaseModel, BaseList, Parsers, ParserMixin, ParsingModel, ParsingView, TagSet, KNOWN_TAGS, ChartOption, ChartOptionList, _ref, _, __slice = [].slice;
 _ref = require('../../util'), _ = _ref._, op = _ref.op;
-_ref = require('../../util/parser'), Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin, ParsingModel = _ref.ParsingModel, ParsingView = _ref.ParsingView;
-_ref = require('../../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
+_ref = require('../../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, Parsers = _ref.Parsers, ParserMixin = _ref.ParserMixin, ParsingModel = _ref.ParsingModel, ParsingView = _ref.ParsingView;
 /**
  * @class A set of tags.
  */
index d5a5dbd..80678c5 100644 (file)
@@ -1,9 +1,9 @@
 var d3, ColorBrewer, op, ChartType, D3ChartElement, root, D3ChartType, _ref, _;
 d3 = require('d3');
 ColorBrewer = require('colorbrewer');
-_ref = require('../../../util'), _ = _ref._, op = _ref.op;
-ChartType = require('../../chart-type').ChartType;
-D3ChartElement = require('./d3-chart-element').D3ChartElement;
+_ref = require('../../util'), _ = _ref._, op = _ref.op;
+ChartType = require('../chart-type').ChartType;
+D3ChartElement = require('./d3/d3-chart-element').D3ChartElement;
 root = function(){
   return this;
 }();
index a84a2d3..a4ee3cc 100644 (file)
@@ -3,9 +3,9 @@ require.define('/node_modules/limn/chart/type/d3-chart.js', function(require, mo
 var d3, ColorBrewer, op, ChartType, D3ChartElement, root, D3ChartType, _ref, _;
 d3 = require('d3');
 ColorBrewer = require('colorbrewer');
-_ref = require('../../../util'), _ = _ref._, op = _ref.op;
-ChartType = require('../../chart-type').ChartType;
-D3ChartElement = require('./d3-chart-element').D3ChartElement;
+_ref = require('../../util'), _ = _ref._, op = _ref.op;
+ChartType = require('../chart-type').ChartType;
+D3ChartElement = require('./d3/d3-chart-element').D3ChartElement;
 root = function(){
   return this;
 }();
index b5228d4..decf44e 100644 (file)
@@ -1,6 +1,6 @@
 var ChartType, DygraphsChartType, _;
-_ = require('../../../util/underscore');
-ChartType = require('../../chart-type').ChartType;
+_ = require('../../util/underscore');
+ChartType = require('../chart-type').ChartType;
 exports.DygraphsChartType = DygraphsChartType = (function(superclass){
   DygraphsChartType.displayName = 'DygraphsChartType';
   var prototype = __extend(DygraphsChartType, superclass).prototype, constructor = DygraphsChartType;
index 44f190a..1f01660 100644 (file)
@@ -1,8 +1,8 @@
 require.define('/node_modules/limn/chart/type/dygraphs.js', function(require, module, exports, __dirname, __filename, undefined){
 
 var ChartType, DygraphsChartType, _;
-_ = require('../../../util/underscore');
-ChartType = require('../../chart-type').ChartType;
+_ = require('../../util/underscore');
+ChartType = require('../chart-type').ChartType;
 exports.DygraphsChartType = DygraphsChartType = (function(superclass){
   DygraphsChartType.displayName = 'DygraphsChartType';
   var prototype = __extend(DygraphsChartType, superclass).prototype, constructor = DygraphsChartType;
diff --git a/lib/client.js b/lib/client.js
new file mode 100644 (file)
index 0000000..ceb7666
--- /dev/null
@@ -0,0 +1,162 @@
+var EventEmitter, op, event, root, limn, emitter, k, Backbone, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _, _i, _len;
+EventEmitter = require('events').EventEmitter;
+_ref = require('./util'), _ = _ref._, op = _ref.op, event = _ref.event, root = _ref.root;
+limn = exports;
+emitter = limn.__emitter__ = new event.ReadyEmitter();
+for (_i = 0, _len = (_ref = ['on', 'addListener', 'off', 'removeListener', 'emit', 'trigger', 'once', 'removeAllListeners']).length; _i < _len; ++_i) {
+  k = _ref[_i];
+  limn[k] = emitter[k].bind(emitter);
+}
+limn.mount = function(path){
+  var mnt, _ref;
+  mnt = ((_ref = limn.config) != null ? _ref.mount : void 8) || '/';
+  return (mnt !== '/' ? mnt : '') + path;
+};
+Backbone = require('backbone');
+_ref = limn.base = require('./base'), BaseView = _ref.BaseView, BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
+_ref = limn.chart = require('./chart'), ChartType = _ref.ChartType, DygraphsChartType = _ref.DygraphsChartType;
+_ref = limn.graph = require('./graph'), Graph = _ref.Graph, GraphList = _ref.GraphList, GraphDisplayView = _ref.GraphDisplayView, GraphEditView = _ref.GraphEditView, GraphListView = _ref.GraphListView;
+_ref = limn.dashboard = require('./dashboard'), DashboardView = _ref.DashboardView, Dashboard = _ref.Dashboard;
+/**
+ * @class Sets up root application, automatically attaching to an existing element
+ *  found at `appSelector` and delegating to the appropriate view.
+ * @extends Backbone.Router
+ */
+LimnApp = limn.LimnApp = Backbone.Router.extend({
+  appSelector: '#content .inner',
+  routes: {
+    'graphs/(new|edit)': 'newGraph',
+    'graphs/:graphId/edit': 'editGraph',
+    'graphs/:graphId': 'showGraph',
+    'graphs': 'listGraphs',
+    'dashboards/(new|edit)': 'newDashboard',
+    'dashboards/:dashId/edit': 'editDashboard',
+    'dashboards/:dashId': 'showDashboard',
+    'dashboards': 'listDashboards'
+  }
+  /**
+   * @constructor
+   */,
+  constructor: (function(){
+    function LimnApp(config){
+      var that;
+      this.config = config != null
+        ? config
+        : {};
+      if (that = config.appSelector) {
+        this.appSelector = that;
+      }
+      this.el = config.el || (config.el = jQuery(this.appSelector)[0]);
+      this.$el = jQuery(this.el);
+      Backbone.Router.call(this, config);
+      return this;
+    }
+    return LimnApp;
+  }()),
+  initialize: function(){
+    var _this = this;
+    jQuery(function(){
+      return _this.setup();
+    });
+    return this;
+  },
+  setup: function(){
+    this.route(/^(?:[\?].*)?$/, 'home');
+    return Backbone.history.start({
+      pushState: true,
+      root: this.config.mount
+    });
+  },
+  processData: function(id, data){
+    data == null && (data = {});
+    if (!(id && _(['edit', 'new']).contains(id))) {
+      data.id = data.slug = id;
+    }
+    return data;
+  }
+  /* * * *  Routes  * * * */,
+  home: function(){
+    return this.showDashboard('reportcard');
+  },
+  createGraphModel: function(id){
+    var data, graph;
+    data = this.processData(id);
+    return graph = new Graph(data, {
+      parse: true
+    });
+  },
+  newGraph: function(){
+    return this.editGraph();
+  },
+  editGraph: function(id){
+    this.model = this.createGraphModel(id);
+    return this.view = new GraphEditView({
+      model: this.model
+    }).attach(this.el);
+  },
+  showGraph: function(id){
+    this.model = this.createGraphModel(id);
+    return this.view = new GraphDisplayView({
+      model: this.model
+    }).attach(this.el);
+  },
+  listGraphs: function(){
+    this.collection = new GraphList();
+    return this.view = new GraphListView({
+      collection: this.collection
+    }).attach(this.el);
+  },
+  createDashboardModel: function(id){
+    var data, dashboard;
+    data = this.processData(id);
+    return dashboard = new Dashboard(data, {
+      parse: true
+    });
+  },
+  newDashboard: function(){
+    return console.error('newDashboard!?');
+  },
+  editDashboard: function(id){
+    return console.error('editDashboard!?');
+  },
+  showDashboard: function(id){
+    this.model = this.createDashboardModel(id);
+    return this.view = new DashboardView({
+      model: this.model
+    }).attach(this.el);
+  },
+  listDashboards: function(){
+    return console.error('listDashboards!?');
+  },
+  getClassName: function(){
+    return (this.constructor.name || this.constructor.displayName) + "";
+  },
+  toString: function(){
+    return this.getClassName() + "()";
+  }
+});
+__import(LimnApp, {
+  findConfig: function(){
+    var config;
+    config = root.limn_config || {};
+    config.mount || (config.mount = "/");
+    return config;
+  },
+  main: (function(){
+    function limnMain(){
+      var config;
+      config = limn.config || (limn.config = LimnApp.findConfig());
+      if (!config.libOnly) {
+        limn.app || (limn.app = new LimnApp(config));
+      }
+      return limn.emit('main', limn.app);
+    }
+    return limnMain;
+  }())
+});
+jQuery(LimnApp.main);
+function __import(obj, src){
+  var own = {}.hasOwnProperty;
+  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
+  return obj;
+}
\ No newline at end of file
diff --git a/lib/client.mod.js b/lib/client.mod.js
new file mode 100644 (file)
index 0000000..2c73b2f
--- /dev/null
@@ -0,0 +1,166 @@
+require.define('/node_modules/limn/client.js', function(require, module, exports, __dirname, __filename, undefined){
+
+var EventEmitter, op, event, root, limn, emitter, k, Backbone, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _, _i, _len;
+EventEmitter = require('events').EventEmitter;
+_ref = require('./util'), _ = _ref._, op = _ref.op, event = _ref.event, root = _ref.root;
+limn = exports;
+emitter = limn.__emitter__ = new event.ReadyEmitter();
+for (_i = 0, _len = (_ref = ['on', 'addListener', 'off', 'removeListener', 'emit', 'trigger', 'once', 'removeAllListeners']).length; _i < _len; ++_i) {
+  k = _ref[_i];
+  limn[k] = emitter[k].bind(emitter);
+}
+limn.mount = function(path){
+  var mnt, _ref;
+  mnt = ((_ref = limn.config) != null ? _ref.mount : void 8) || '/';
+  return (mnt !== '/' ? mnt : '') + path;
+};
+Backbone = require('backbone');
+_ref = limn.base = require('./base'), BaseView = _ref.BaseView, BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
+_ref = limn.chart = require('./chart'), ChartType = _ref.ChartType, DygraphsChartType = _ref.DygraphsChartType;
+_ref = limn.graph = require('./graph'), Graph = _ref.Graph, GraphList = _ref.GraphList, GraphDisplayView = _ref.GraphDisplayView, GraphEditView = _ref.GraphEditView, GraphListView = _ref.GraphListView;
+_ref = limn.dashboard = require('./dashboard'), DashboardView = _ref.DashboardView, Dashboard = _ref.Dashboard;
+/**
+ * @class Sets up root application, automatically attaching to an existing element
+ *  found at `appSelector` and delegating to the appropriate view.
+ * @extends Backbone.Router
+ */
+LimnApp = limn.LimnApp = Backbone.Router.extend({
+  appSelector: '#content .inner',
+  routes: {
+    'graphs/(new|edit)': 'newGraph',
+    'graphs/:graphId/edit': 'editGraph',
+    'graphs/:graphId': 'showGraph',
+    'graphs': 'listGraphs',
+    'dashboards/(new|edit)': 'newDashboard',
+    'dashboards/:dashId/edit': 'editDashboard',
+    'dashboards/:dashId': 'showDashboard',
+    'dashboards': 'listDashboards'
+  }
+  /**
+   * @constructor
+   */,
+  constructor: (function(){
+    function LimnApp(config){
+      var that;
+      this.config = config != null
+        ? config
+        : {};
+      if (that = config.appSelector) {
+        this.appSelector = that;
+      }
+      this.el = config.el || (config.el = jQuery(this.appSelector)[0]);
+      this.$el = jQuery(this.el);
+      Backbone.Router.call(this, config);
+      return this;
+    }
+    return LimnApp;
+  }()),
+  initialize: function(){
+    var _this = this;
+    jQuery(function(){
+      return _this.setup();
+    });
+    return this;
+  },
+  setup: function(){
+    this.route(/^(?:[\?].*)?$/, 'home');
+    return Backbone.history.start({
+      pushState: true,
+      root: this.config.mount
+    });
+  },
+  processData: function(id, data){
+    data == null && (data = {});
+    if (!(id && _(['edit', 'new']).contains(id))) {
+      data.id = data.slug = id;
+    }
+    return data;
+  }
+  /* * * *  Routes  * * * */,
+  home: function(){
+    return this.showDashboard('reportcard');
+  },
+  createGraphModel: function(id){
+    var data, graph;
+    data = this.processData(id);
+    return graph = new Graph(data, {
+      parse: true
+    });
+  },
+  newGraph: function(){
+    return this.editGraph();
+  },
+  editGraph: function(id){
+    this.model = this.createGraphModel(id);
+    return this.view = new GraphEditView({
+      model: this.model
+    }).attach(this.el);
+  },
+  showGraph: function(id){
+    this.model = this.createGraphModel(id);
+    return this.view = new GraphDisplayView({
+      model: this.model
+    }).attach(this.el);
+  },
+  listGraphs: function(){
+    this.collection = new GraphList();
+    return this.view = new GraphListView({
+      collection: this.collection
+    }).attach(this.el);
+  },
+  createDashboardModel: function(id){
+    var data, dashboard;
+    data = this.processData(id);
+    return dashboard = new Dashboard(data, {
+      parse: true
+    });
+  },
+  newDashboard: function(){
+    return console.error('newDashboard!?');
+  },
+  editDashboard: function(id){
+    return console.error('editDashboard!?');
+  },
+  showDashboard: function(id){
+    this.model = this.createDashboardModel(id);
+    return this.view = new DashboardView({
+      model: this.model
+    }).attach(this.el);
+  },
+  listDashboards: function(){
+    return console.error('listDashboards!?');
+  },
+  getClassName: function(){
+    return (this.constructor.name || this.constructor.displayName) + "";
+  },
+  toString: function(){
+    return this.getClassName() + "()";
+  }
+});
+__import(LimnApp, {
+  findConfig: function(){
+    var config;
+    config = root.limn_config || {};
+    config.mount || (config.mount = "/");
+    return config;
+  },
+  main: (function(){
+    function limnMain(){
+      var config;
+      config = limn.config || (limn.config = LimnApp.findConfig());
+      if (!config.libOnly) {
+        limn.app || (limn.app = new LimnApp(config));
+      }
+      return limn.emit('main', limn.app);
+    }
+    return limnMain;
+  }())
+});
+jQuery(LimnApp.main);
+function __import(obj, src){
+  var own = {}.hasOwnProperty;
+  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
+  return obj;
+}
+
+});
index 9af7647..5dc2b7e 100644 (file)
@@ -1,4 +1,5 @@
-var op, TimeSeriesData, CSVData, BaseModel, BaseList, ModelCache, Metric, MetricList, DataSource, DataSourceList, ALL_SOURCES, sourceCache, _ref, _;
+var limn, op, TimeSeriesData, CSVData, BaseModel, BaseList, ModelCache, Metric, MetricList, DataSource, DataSourceList, ALL_SOURCES, sourceCache, _ref, _;
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 _ref = require('../util/timeseries'), TimeSeriesData = _ref.TimeSeriesData, CSVData = _ref.CSVData;
 _ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, ModelCache = _ref.ModelCache;
@@ -208,9 +209,11 @@ sourceCache = new ModelCache(DataSource, {
   ready: false,
   cache: ALL_SOURCES
 });
-$.getJSON('/datasources/all', function(data){
-  ALL_SOURCES.reset(_.map(data, op.I));
-  return sourceCache.triggerReady();
+limn.on('main', function(){
+  return $.getJSON(limn.mount('/datasources/all'), function(data){
+    ALL_SOURCES.reset(_.map(data, op.I));
+    return sourceCache.triggerReady();
+  });
 });
 DataSource.getAllSources = function(){
   return ALL_SOURCES;
index 30a8182..132f27c 100644 (file)
@@ -1,6 +1,7 @@
 require.define('/node_modules/limn/data/datasource-model.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var op, TimeSeriesData, CSVData, BaseModel, BaseList, ModelCache, Metric, MetricList, DataSource, DataSourceList, ALL_SOURCES, sourceCache, _ref, _;
+var limn, op, TimeSeriesData, CSVData, BaseModel, BaseList, ModelCache, Metric, MetricList, DataSource, DataSourceList, ALL_SOURCES, sourceCache, _ref, _;
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, op = _ref.op;
 _ref = require('../util/timeseries'), TimeSeriesData = _ref.TimeSeriesData, CSVData = _ref.CSVData;
 _ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, ModelCache = _ref.ModelCache;
@@ -210,9 +211,11 @@ sourceCache = new ModelCache(DataSource, {
   ready: false,
   cache: ALL_SOURCES
 });
-$.getJSON('/datasources/all', function(data){
-  ALL_SOURCES.reset(_.map(data, op.I));
-  return sourceCache.triggerReady();
+limn.on('main', function(){
+  return $.getJSON(limn.mount('/datasources/all'), function(data){
+    ALL_SOURCES.reset(_.map(data, op.I));
+    return sourceCache.triggerReady();
+  });
 });
 DataSource.getAllSources = function(){
   return ALL_SOURCES;
index 79428cd..78110e5 100644 (file)
@@ -1,5 +1,6 @@
-var Seq, Cascade, BaseModel, BaseList, ModelCache, ChartType, DataSet, root, Graph, GraphList, _ref, _;
+var Seq, limn, Cascade, BaseModel, BaseList, ModelCache, ChartType, DataSet, root, Graph, GraphList, _ref, _;
 Seq = require('seq');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, Cascade = _ref.Cascade;
 _ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, ModelCache = _ref.ModelCache;
 ChartType = require('../chart').ChartType;
@@ -68,7 +69,7 @@ Graph = exports.Graph = BaseModel.extend({
     };
   },
   url: function(){
-    return this.urlRoot + "/" + this.get('slug') + ".json";
+    return limn.mount(this.urlRoot) + "/" + this.get('slug') + ".json";
   },
   constructor: (function(){
     function Graph(attributes, opts){
@@ -405,7 +406,7 @@ Graph = exports.Graph = BaseModel.extend({
   toURL: function(action){
     var slug, path;
     slug = this.get('slug');
-    path = _.compact([this.urlRoot, slug, action]).join('/');
+    path = limn.mount(_.compact([this.urlRoot, slug, action])).join('/');
     return path + "?" + this.toKV({
       keepSlug: !!slug
     });
@@ -414,7 +415,7 @@ Graph = exports.Graph = BaseModel.extend({
    * @returns {String} Path portion of slug URL, e.g.  /graphs/:slug
    */,
   toLink: function(){
-    return this.urlRoot + "/" + this.get('slug');
+    return limn.mount(this.urlRoot) + "/" + this.get('slug');
   }
   /**
    * @returns {String} Permalinked URI, e.g. http://reportcard.wmflabs.org/:slug
index c511d60..74625d9 100644 (file)
@@ -1,7 +1,8 @@
 require.define('/node_modules/limn/graph/graph-model.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var Seq, Cascade, BaseModel, BaseList, ModelCache, ChartType, DataSet, root, Graph, GraphList, _ref, _;
+var Seq, limn, Cascade, BaseModel, BaseList, ModelCache, ChartType, DataSet, root, Graph, GraphList, _ref, _;
 Seq = require('seq');
+limn = require('../client');
 _ref = require('../util'), _ = _ref._, Cascade = _ref.Cascade;
 _ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, ModelCache = _ref.ModelCache;
 ChartType = require('../chart').ChartType;
@@ -70,7 +71,7 @@ Graph = exports.Graph = BaseModel.extend({
     };
   },
   url: function(){
-    return this.urlRoot + "/" + this.get('slug') + ".json";
+    return limn.mount(this.urlRoot) + "/" + this.get('slug') + ".json";
   },
   constructor: (function(){
     function Graph(attributes, opts){
@@ -407,7 +408,7 @@ Graph = exports.Graph = BaseModel.extend({
   toURL: function(action){
     var slug, path;
     slug = this.get('slug');
-    path = _.compact([this.urlRoot, slug, action]).join('/');
+    path = limn.mount(_.compact([this.urlRoot, slug, action])).join('/');
     return path + "?" + this.toKV({
       keepSlug: !!slug
     });
@@ -416,7 +417,7 @@ Graph = exports.Graph = BaseModel.extend({
    * @returns {String} Path portion of slug URL, e.g.  /graphs/:slug
    */,
   toLink: function(){
-    return this.urlRoot + "/" + this.get('slug');
+    return limn.mount(this.urlRoot) + "/" + this.get('slug');
   }
   /**
    * @returns {String} Permalinked URI, e.g. http://reportcard.wmflabs.org/:slug
index 979a5f9..ceb7666 100644 (file)
@@ -1,7 +1,18 @@
-var limn, Backbone, op, root, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _;
+var EventEmitter, op, event, root, limn, emitter, k, Backbone, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _, _i, _len;
+EventEmitter = require('events').EventEmitter;
+_ref = require('./util'), _ = _ref._, op = _ref.op, event = _ref.event, root = _ref.root;
 limn = exports;
+emitter = limn.__emitter__ = new event.ReadyEmitter();
+for (_i = 0, _len = (_ref = ['on', 'addListener', 'off', 'removeListener', 'emit', 'trigger', 'once', 'removeAllListeners']).length; _i < _len; ++_i) {
+  k = _ref[_i];
+  limn[k] = emitter[k].bind(emitter);
+}
+limn.mount = function(path){
+  var mnt, _ref;
+  mnt = ((_ref = limn.config) != null ? _ref.mount : void 8) || '/';
+  return (mnt !== '/' ? mnt : '') + path;
+};
 Backbone = require('backbone');
-_ref = limn.util = require('./util'), _ = _ref._, op = _ref.op, root = _ref.root;
 _ref = limn.base = require('./base'), BaseView = _ref.BaseView, BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
 _ref = limn.chart = require('./chart'), ChartType = _ref.ChartType, DygraphsChartType = _ref.DygraphsChartType;
 _ref = limn.graph = require('./graph'), Graph = _ref.Graph, GraphList = _ref.GraphList, GraphDisplayView = _ref.GraphDisplayView, GraphEditView = _ref.GraphEditView, GraphListView = _ref.GraphListView;
@@ -136,8 +147,9 @@ __import(LimnApp, {
       var config;
       config = limn.config || (limn.config = LimnApp.findConfig());
       if (!config.libOnly) {
-        return limn.app || (limn.app = new LimnApp(config));
+        limn.app || (limn.app = new LimnApp(config));
       }
+      return limn.emit('main', limn.app);
     }
     return limnMain;
   }())
index 904f2eb..ce7fd7b 100644 (file)
@@ -1,9 +1,20 @@
 require.define('/node_modules/limn/limn.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var limn, Backbone, op, root, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _;
+var EventEmitter, op, event, root, limn, emitter, k, Backbone, BaseView, BaseModel, BaseList, ChartType, DygraphsChartType, Graph, GraphList, GraphDisplayView, GraphEditView, GraphListView, DashboardView, Dashboard, LimnApp, _ref, _, _i, _len;
+EventEmitter = require('events').EventEmitter;
+_ref = require('./util'), _ = _ref._, op = _ref.op, event = _ref.event, root = _ref.root;
 limn = exports;
+emitter = limn.__emitter__ = new event.ReadyEmitter();
+for (_i = 0, _len = (_ref = ['on', 'addListener', 'off', 'removeListener', 'emit', 'trigger', 'once', 'removeAllListeners']).length; _i < _len; ++_i) {
+  k = _ref[_i];
+  limn[k] = emitter[k].bind(emitter);
+}
+limn.mount = function(path){
+  var mnt, _ref;
+  mnt = ((_ref = limn.config) != null ? _ref.mount : void 8) || '/';
+  return (mnt !== '/' ? mnt : '') + path;
+};
 Backbone = require('backbone');
-_ref = limn.util = require('./util'), _ = _ref._, op = _ref.op, root = _ref.root;
 _ref = limn.base = require('./base'), BaseView = _ref.BaseView, BaseModel = _ref.BaseModel, BaseList = _ref.BaseList;
 _ref = limn.chart = require('./chart'), ChartType = _ref.ChartType, DygraphsChartType = _ref.DygraphsChartType;
 _ref = limn.graph = require('./graph'), Graph = _ref.Graph, GraphList = _ref.GraphList, GraphDisplayView = _ref.GraphDisplayView, GraphEditView = _ref.GraphEditView, GraphListView = _ref.GraphListView;
@@ -138,8 +149,9 @@ __import(LimnApp, {
       var config;
       config = limn.config || (limn.config = LimnApp.findConfig());
       if (!config.libOnly) {
-        return limn.app || (limn.app = new LimnApp(config));
+        limn.app || (limn.app = new LimnApp(config));
       }
+      return limn.emit('main', limn.app);
     }
     return limnMain;
   }())
index 96273d8..de5d5fc 100644 (file)
@@ -128,14 +128,21 @@ application = limn.application = {
     this.set('limn options', opts);
     mkdirp(opts.dataDir);
     this.configure(function(){
+      var view_opts;
       this.set('views', WWW);
       this.set('view engine', 'jade');
-      this.set('view options', __import({
+      view_opts = __import({
         layout: false,
+        config: this.set('limn options'),
         version: REV,
         IS_DEV: IS_DEV,
-        IS_PROD: IS_PROD
-      }, require('./view-helpers')));
+        IS_PROD: IS_PROD,
+        REV: REV
+      }, require('./view-helpers'));
+      view_opts.__defineGetter__('mount', function(){
+        return app.route || '/';
+      });
+      this.set('view options', view_opts);
       this.use(require('./reqinfo')({}));
       this.use(express.bodyParser());
       this.use(express.methodOverride());
index 9b65e75..f4ac278 100644 (file)
@@ -12,14 +12,15 @@ app = exports = module.exports = express.createServer();
 /**
  * Load Limn middleware
  */
-app.use(limn = app.limn = LimnMiddleware({
+limn = app.limn = LimnMiddleware({
   varDir: './var',
   dataDir: './var/data',
   proxy: {
     enabled: true,
     whitelist: /.*/
   }
-}));
+});
+app.use(limn);
 app.use(express.errorHandler({
   dumpExceptions: true,
   showStack: true
index 8185164..36fc44b 100644 (file)
@@ -1,4 +1,4 @@
-var op, root, backbone, parser, Cascade, _, _ref, __slice = [].slice;
+var op, root, event, backbone, parser, Cascade, _, _ref, __slice = [].slice;
 _ = exports._ = require('./underscore');
 op = exports.op = require('./op');
 root = exports.root = function(){
@@ -32,7 +32,8 @@ if ((_ref = root.jQuery) != null) {
     return _results;
   };
 }
-__import(exports, require('./event'));
+event = exports.event = require('./event');
+__import(exports, event);
 backbone = exports.backbone = require('./backbone');
 parser = exports.parser = require('./parser');
 Cascade = exports.Cascade = require('./cascade');
index 7c5fe4b..8013081 100644 (file)
@@ -1,6 +1,6 @@
 require.define('/node_modules/limn/util/index.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var op, root, backbone, parser, Cascade, _, _ref, __slice = [].slice;
+var op, root, event, backbone, parser, Cascade, _, _ref, __slice = [].slice;
 _ = exports._ = require('./underscore');
 op = exports.op = require('./op');
 root = exports.root = function(){
@@ -34,7 +34,8 @@ if ((_ref = root.jQuery) != null) {
     return _results;
   };
 }
-__import(exports, require('./event'));
+event = exports.event = require('./event');
+__import(exports, event);
 backbone = exports.backbone = require('./backbone');
 parser = exports.parser = require('./parser');
 Cascade = exports.Cascade = require('./cascade');
index e2fa110..10c94bd 100644 (file)
@@ -1,7 +1,6 @@
-var op, BaseModel, BaseList, BaseView, Mixin, Parsers, ParserMixin, ParsingModel, ParsingList, ParsingView, _, _ref;
+var op, Parsers, _;
 _ = require('./underscore');
 op = require('./op');
-_ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, BaseView = _ref.BaseView, Mixin = _ref.Mixin;
 /**
  * @namespace Parsers by type.
  */
@@ -74,100 +73,4 @@ Parsers = exports.Parsers = {
     }
   }
 };
-Parsers.parseNumber = Parsers.parseFloat;
-/**
- * @class Methods for a class to select parsers by type reflection.
- * @mixin
- */
-exports.ParserMixin = ParserMixin = (function(superclass){
-  ParserMixin.displayName = 'ParserMixin';
-  var prototype = __extend(ParserMixin, superclass).prototype, constructor = ParserMixin;
-  __import(ParserMixin.prototype, Parsers);
-  function ParserMixin(target){
-    return Mixin.call(ParserMixin, target);
-  }
-  prototype.parseValue = function(v, type){
-    return this.getParser(type)(v);
-  };
-  prototype.getParser = function(type){
-    var fn, t, _i, _ref, _len;
-    type == null && (type = 'String');
-    fn = this["parse" + type];
-    if (typeof fn === 'function') {
-      return fn;
-    }
-    type = _(String(type).toLowerCase());
-    for (_i = 0, _len = (_ref = ['Integer', 'Float', 'Number', 'Boolean', 'Object', 'Array', 'Function']).length; _i < _len; ++_i) {
-      t = _ref[_i];
-      if (type.startsWith(t.toLowerCase())) {
-        return this["parse" + t];
-      }
-    }
-    return this.defaultParser || this.parseString;
-  };
-  prototype.getParserFromExample = function(v){
-    var type;
-    if (v == null) {
-      return null;
-    }
-    type = typeof v;
-    if (type !== 'object') {
-      return this.getParser(type);
-    } else if (_.isArray(v)) {
-      return this.getParser('Array');
-    } else {
-      return this.getParser('Object');
-    }
-  };
-  return ParserMixin;
-}(Mixin));
-/**
- * @class Basic model which mixes in the ParserMixin.
- * @extends BaseModel
- * @borrows ParserMixin
- */
-ParsingModel = exports.ParsingModel = BaseModel.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingModel(){
-      return BaseModel.apply(this, arguments);
-    }
-    return ParsingModel;
-  }())
-}));
-/**
- * @class Basic collection which mixes in the ParserMixin.
- * @extends BaseList
- * @borrows ParserMixin
- */
-ParsingList = exports.ParsingList = BaseList.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingList(){
-      return BaseList.apply(this, arguments);
-    }
-    return ParsingList;
-  }())
-}));
-/**
- * @class Basic view which mixes in the ParserMixin.
- * @extends BaseView
- * @borrows ParserMixin
- */
-ParsingView = exports.ParsingView = BaseView.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingView(){
-      return BaseView.apply(this, arguments);
-    }
-    return ParsingView;
-  }())
-}));
-function __extend(sub, sup){
-  function fun(){} fun.prototype = (sub.superclass = sup).prototype;
-  (sub.prototype = new fun).constructor = sub;
-  if (typeof sup.extended == 'function') sup.extended(sub);
-  return sub;
-}
-function __import(obj, src){
-  var own = {}.hasOwnProperty;
-  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
-  return obj;
-}
\ No newline at end of file
+Parsers.parseNumber = Parsers.parseFloat;
\ No newline at end of file
index a6dda3c..0c26c28 100644 (file)
@@ -1,9 +1,8 @@
 require.define('/node_modules/limn/util/parser.js', function(require, module, exports, __dirname, __filename, undefined){
 
-var op, BaseModel, BaseList, BaseView, Mixin, Parsers, ParserMixin, ParsingModel, ParsingList, ParsingView, _, _ref;
+var op, Parsers, _;
 _ = require('./underscore');
 op = require('./op');
-_ref = require('../base'), BaseModel = _ref.BaseModel, BaseList = _ref.BaseList, BaseView = _ref.BaseView, Mixin = _ref.Mixin;
 /**
  * @namespace Parsers by type.
  */
@@ -77,101 +76,5 @@ Parsers = exports.Parsers = {
   }
 };
 Parsers.parseNumber = Parsers.parseFloat;
-/**
- * @class Methods for a class to select parsers by type reflection.
- * @mixin
- */
-exports.ParserMixin = ParserMixin = (function(superclass){
-  ParserMixin.displayName = 'ParserMixin';
-  var prototype = __extend(ParserMixin, superclass).prototype, constructor = ParserMixin;
-  __import(ParserMixin.prototype, Parsers);
-  function ParserMixin(target){
-    return Mixin.call(ParserMixin, target);
-  }
-  prototype.parseValue = function(v, type){
-    return this.getParser(type)(v);
-  };
-  prototype.getParser = function(type){
-    var fn, t, _i, _ref, _len;
-    type == null && (type = 'String');
-    fn = this["parse" + type];
-    if (typeof fn === 'function') {
-      return fn;
-    }
-    type = _(String(type).toLowerCase());
-    for (_i = 0, _len = (_ref = ['Integer', 'Float', 'Number', 'Boolean', 'Object', 'Array', 'Function']).length; _i < _len; ++_i) {
-      t = _ref[_i];
-      if (type.startsWith(t.toLowerCase())) {
-        return this["parse" + t];
-      }
-    }
-    return this.defaultParser || this.parseString;
-  };
-  prototype.getParserFromExample = function(v){
-    var type;
-    if (v == null) {
-      return null;
-    }
-    type = typeof v;
-    if (type !== 'object') {
-      return this.getParser(type);
-    } else if (_.isArray(v)) {
-      return this.getParser('Array');
-    } else {
-      return this.getParser('Object');
-    }
-  };
-  return ParserMixin;
-}(Mixin));
-/**
- * @class Basic model which mixes in the ParserMixin.
- * @extends BaseModel
- * @borrows ParserMixin
- */
-ParsingModel = exports.ParsingModel = BaseModel.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingModel(){
-      return BaseModel.apply(this, arguments);
-    }
-    return ParsingModel;
-  }())
-}));
-/**
- * @class Basic collection which mixes in the ParserMixin.
- * @extends BaseList
- * @borrows ParserMixin
- */
-ParsingList = exports.ParsingList = BaseList.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingList(){
-      return BaseList.apply(this, arguments);
-    }
-    return ParsingList;
-  }())
-}));
-/**
- * @class Basic view which mixes in the ParserMixin.
- * @extends BaseView
- * @borrows ParserMixin
- */
-ParsingView = exports.ParsingView = BaseView.extend(ParserMixin.mix({
-  constructor: (function(){
-    function ParsingView(){
-      return BaseView.apply(this, arguments);
-    }
-    return ParsingView;
-  }())
-}));
-function __extend(sub, sup){
-  function fun(){} fun.prototype = (sub.superclass = sup).prototype;
-  (sub.prototype = new fun).constructor = sub;
-  if (typeof sup.extended == 'function') sup.extended(sub);
-  return sub;
-}
-function __import(obj, src){
-  var own = {}.hasOwnProperty;
-  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
-  return obj;
-}
 
 });
index 7219b6a..a60acc9 100644 (file)
@@ -1,5 +1,6 @@
 Backbone = require 'backbone'
 
+limn = require '../client'
 { _, op,
 } = require '../util'
 { BaseBackboneMixin, mixinBase,
@@ -26,7 +27,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
     ### Accessors
     
     url: ->
-        "#{@urlRoot}/#{@get('id')}.json"
+        "#{limn.mount @urlRoot}/#{@get('id')}.json"
     
     has: (key) ->
         @get(key)?
@@ -228,11 +229,12 @@ BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
         @models.map -> it.id or it.get('id') or it.cid
     
     url: ->
+        root = limn.mount @urlRoot
         id = @get('id') or @get('slug')
         if id
-            "#{@urlRoot}/#id.json"
+            "#root/#id.json"
         else
-            "#{@urlRoot}.json"
+            "#root.json"
     
     
     load: ->
index 5224bca..aa20b32 100644 (file)
@@ -25,7 +25,7 @@ class Base extends EventEmitter
         @__superclass__ = @..superclass
         @__apply_bind__()
         super()
-        @__class__.emit 'new', this
+        @__class__.emit? 'new', this
     
     
     ### Auto-Bound methods
@@ -44,6 +44,7 @@ class Base extends EventEmitter
         _.bindAll this, ...names if names.length
     
     
+    ### Misc
     
     getClassName: ->
         "#{@..name or @..displayName}"
@@ -57,8 +58,8 @@ class Base extends EventEmitter
     
     @extended = (Subclass) ->
         # copy over all class methods, including this
-        for own k, v in this
-            Subclass[k] = v if typeof v is 'function'
+        for k, v in this
+            Subclass[k] = v if typeof v is 'function' and not _.contains <[ apply call constructor toString ]>, k
         Subclass.__super__ = @::
         Subclass
     
index 4a62daf..0d5abc8 100644 (file)
@@ -7,3 +7,6 @@ cascading    = require './cascading-model'
 data_binding = require './data-binding'
 exports import mixins   import models       import views \
         import cache    import cascading    import data_binding
+
+parser_mixin = require './parser-mixin'
+exports import parser_mixin
diff --git a/src/base/parser-mixin.co b/src/base/parser-mixin.co
new file mode 100644 (file)
index 0000000..d82d255
--- /dev/null
@@ -0,0 +1,86 @@
+{ Parsers,
+} = require '../util/parser'
+{ BaseModel, BaseList, BaseView, Mixin,
+}  = require '../base'
+
+exports.Parsers = Parsers
+
+/**
+ * @class Methods for a class to select parsers by type reflection.
+ * @mixin
+ */
+class exports.ParserMixin extends Mixin
+    this:: import Parsers
+    
+    (target) ->
+        return Mixin.call ParserMixin, target
+    
+    
+    # XXX: So I'm meh about mixing in the Parsers dictionary.
+    # 
+    # - Pros: mixing in `parseXXX()` methods makes it easy to
+    #   override in the target class.
+    # - Cons: `parse()` is a Backbone method, which bit me once
+    #   already (hence `parseValue()`), so conflicts aren't unlikely.
+    # 
+    # Other ideas:
+    # - Parsers live at `@__parsers__`, and each instance gets its own clone
+    # -> Parser lookup uses a Cascade from the object. (Why not just use prototype, tho?)
+    
+    parseValue: (v, type) ->
+        @getParser(type)(v)
+    
+    getParser: (type='String') ->
+        # If this is a known type and we have a parser for it, return that
+        fn = @["parse#type"]
+        return fn if typeof fn is 'function'
+        
+        # Handle compound/optional types
+        # XXX: handle 'or' by returning an array of parsers?
+        type = _ String(type).toLowerCase()
+        for t of <[ Integer Float Number Boolean Object Array Function ]>
+            if type.startsWith t.toLowerCase()
+                return @["parse#t"]
+        @defaultParser or @parseString
+    
+    getParserFromExample: (v) ->
+        return null unless v?
+        type = typeof v
+        
+        if type is not 'object'
+            @getParser type
+        else if _.isArray v
+            @getParser 'Array'
+        else
+            @getParser 'Object'
+    
+
+
+
+/**
+ * @class Basic model which mixes in the ParserMixin.
+ * @extends BaseModel
+ * @borrows ParserMixin
+ */
+ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin.mix do
+    constructor: function ParsingModel then BaseModel ...
+
+
+/**
+ * @class Basic collection which mixes in the ParserMixin.
+ * @extends BaseList
+ * @borrows ParserMixin
+ */
+ParsingList = exports.ParsingList = BaseList.extend ParserMixin.mix do
+    constructor: function ParsingList then BaseList ...
+
+
+/**
+ * @class Basic view which mixes in the ParserMixin.
+ * @extends BaseView
+ * @borrows ParserMixin
+ */
+ParsingView = exports.ParsingView = BaseView.extend ParserMixin.mix do
+    constructor: function ParsingView then BaseView ...
+
+
index adbfc92..7d45cf8 100644 (file)
@@ -1,12 +1,13 @@
 moment = require 'moment'
 Backbone = require 'backbone'
 
+limn = require '../client'
 { _, op,
 } = require '../util'
 { ReadyEmitter,
 } = require '../util/event'
 { Parsers, ParserMixin,
-} = require '../util/parser'
+} = require '../base'
 
 
 
@@ -159,11 +160,12 @@ class exports.ChartType extends ReadyEmitter
      */
     loadSpec: ->
         return this if @ready
-        proto = @constructor::        
+        proto = @constructor::
+        url = (if limn.config.mount is not '/' then limn.config.mount else '') + @SPEC_URL
         jQuery.ajax do
-            url     : @SPEC_URL
+            url      : url
             dataType : 'json'
-            success : (spec) ~>
+            success  : (spec) ~>
                 proto.spec = spec
                 proto.options_ordered = spec
                 proto.options = _.synthesize spec, -> [it.name, it]
index 458133b..5f7a5db 100644 (file)
@@ -1,8 +1,8 @@
 { _, op,
 } = require '../../util'
-{ Parsers, ParserMixin, ParsingModel, ParsingView,
-} = require '../../util/parser'
-{ BaseModel, BaseList,
+{
+    BaseModel, BaseList,
+    Parsers, ParserMixin, ParsingModel, ParsingView,
 }  = require '../../base'
 
 
index 9f0e321..e4756cd 100644 (file)
@@ -2,11 +2,11 @@ d3 = require 'd3'
 ColorBrewer = require 'colorbrewer'
 
 { _, op,
-} = require '../../../util'
+} = require '../../util'
 { ChartType,
-} = require '../../chart-type'
+} = require '../chart-type'
 { D3ChartElement,
-} = require './d3-chart-element'
+} = require './d3/d3-chart-element'
 
 
 root = do -> this
index 00a0bcd..8490e1b 100644 (file)
@@ -1,6 +1,6 @@
-_ = require '../../../util/underscore'
+_ = require '../../util/underscore'
 { ChartType,
-} = require '../../chart-type'
+} = require '../chart-type'
 
 
 class exports.DygraphsChartType extends ChartType
similarity index 89%
rename from src/limn.co
rename to src/client.co
index a7224e1..22acef6 100644 (file)
@@ -1,9 +1,21 @@
+{EventEmitter} = require 'events'
+
+{ _, op, event, root,
+} = require './util'
+
+# Decorate root limn namespace object with EventEmitter methods
 limn = exports
+emitter = limn.__emitter__ = new event.ReadyEmitter()
+for k of <[ on addListener off removeListener emit trigger once removeAllListeners ]>
+    limn[k] = emitter[k].bind emitter
+
+limn.mount = (path) ->
+    mnt = limn.config?.mount or '/'
+    (if mnt is not '/' then mnt else '') + path
+
 
 Backbone = require 'backbone'
 
-{ _, op, root,
-} = limn.util       = require './util'
 { BaseView, BaseModel, BaseList,
 } = limn.base       = require './base'
 { ChartType, DygraphsChartType,
@@ -143,7 +155,7 @@ LimnApp import do
     main : function limnMain
         config = limn.config or= LimnApp.findConfig()
         limn.app or= new LimnApp config unless config.libOnly
+        limn.emit 'main', limn.app
 
 
 jQuery LimnApp.main
-
index 84c3b52..d3378ac 100644 (file)
@@ -1,3 +1,4 @@
+limn = require '../client'
 { _, op,
 } = require '../util'
 { TimeSeriesData, CSVData,
@@ -180,9 +181,10 @@ ALL_SOURCES = new DataSourceList
 sourceCache = new ModelCache DataSource, {-ready, cache:ALL_SOURCES}
 
 # Fetch all DataSources
-$.getJSON '/datasources/all', (data) ->
-    ALL_SOURCES.reset _.map data, op.I
-    sourceCache.triggerReady()
+limn.on 'main', ->
+    $.getJSON limn.mount('/datasources/all'), (data) ->
+        ALL_SOURCES.reset _.map data, op.I
+        sourceCache.triggerReady()
 
 DataSource.getAllSources = ->
     ALL_SOURCES
index 5d825d5..911e1bf 100644 (file)
@@ -1,5 +1,6 @@
 Seq = require 'seq'
 
+limn = require '../client'
 { _, Cascade,
 } = require '../util'
 { BaseModel, BaseList, ModelCache,
@@ -79,7 +80,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{
         options : {}
     
     url: ->
-        "#{@urlRoot}/#{@get('slug')}.json"
+        "#{limn.mount @urlRoot}/#{@get('slug')}.json"
     
     
     
@@ -396,14 +397,14 @@ Graph = exports.Graph = BaseModel.extend do # {{{
      */
     toURL: (action) ->
         slug = @get 'slug'
-        path = _.compact [ @urlRoot, slug, action ] .join '/'
+        path = limn.mount _.compact [ @urlRoot, slug, action ] .join '/'
         "#path?#{@toKV { keepSlug: !!slug }}"
     
     /**
      * @returns {String} Path portion of slug URL, e.g.  /graphs/:slug
      */
     toLink: ->
-        "#{@urlRoot}/#{@get('slug')}"
+        "#{limn.mount @urlRoot}/#{@get('slug')}"
     
     /**
      * @returns {String} Permalinked URI, e.g. http://reportcard.wmflabs.org/:slug
index 37f5743..c9e5b08 100755 (executable)
@@ -1,6 +1,5 @@
 #!/usr/bin/env node
 var coco = require('coco')
-,   coffee = require('coffee-script')
 ,   server = require('./server')
 ;
 
index aa15ec1..69c1eb6 100755 (executable)
@@ -153,12 +153,15 @@ application = limn.application =
         @configure ->
             @set 'views', WWW
             @set 'view engine', 'jade'
-            @set 'view options', {
+            
+            view_opts = {
                 layout  : false
+                config  : @set('limn options')
                 version : REV
-                IS_DEV, IS_PROD
+                IS_DEV, IS_PROD, REV
             } import require './view-helpers'
-            # @helpers require './view-helpers'
+            view_opts.__defineGetter__ 'mount', -> app.route or '/'
+            @set 'view options', view_opts
             
             # Parse URL, fiddle
             @use require('./reqinfo')({})
index 2aa38db..ca82510 100755 (executable)
@@ -17,13 +17,16 @@ app = exports = module.exports = express.createServer()
 /**
  * Load Limn middleware
  */
-app.use limn = app.limn = LimnMiddleware do
+limn = app.limn = LimnMiddleware do
     varDir  : './var'
     dataDir : './var/data'
     proxy :
         enabled   : true
         whitelist : /.*/
 
+app.use limn
+# app.use '/vis', limn
+
 # show exceptions, pretty stack traces ### FIXME
 app.use express.errorHandler { +dumpExceptions, +showStack }
 
index 1686b86..48f813f 100644 (file)
@@ -26,7 +26,8 @@ root.jQuery?.fn.invoke = (method, ...args) ->
         jQuery(el)[method] ...args
 
 
-exports import require './event'
+event = exports.event = require './event'
+exports import event
 
 backbone = exports.backbone = require './backbone'
 parser   = exports.parser   = require './parser'
index b9879f3..e951343 100644 (file)
@@ -1,7 +1,5 @@
 _  = require './underscore'
 op = require './op'
-{ BaseModel, BaseList, BaseView, Mixin,
-}  = require '../base'
 
 
 /**
@@ -45,83 +43,3 @@ Parsers = exports.Parsers =
 # Aliases
 Parsers.parseNumber = Parsers.parseFloat
 
-
-/**
- * @class Methods for a class to select parsers by type reflection.
- * @mixin
- */
-class exports.ParserMixin extends Mixin
-    this:: import Parsers
-    
-    (target) ->
-        return Mixin.call ParserMixin, target
-    
-    
-    # XXX: So I'm meh about mixing in the Parsers dictionary.
-    # 
-    # - Pros: mixing in `parseXXX()` methods makes it easy to
-    #   override in the target class.
-    # - Cons: `parse()` is a Backbone method, which bit me once
-    #   already (hence `parseValue()`), so conflicts aren't unlikely.
-    # 
-    # Other ideas:
-    # - Parsers live at `@__parsers__`, and each instance gets its own clone
-    # -> Parser lookup uses a Cascade from the object. (Why not just use prototype, tho?)
-    
-    parseValue: (v, type) ->
-        @getParser(type)(v)
-    
-    getParser: (type='String') ->
-        # If this is a known type and we have a parser for it, return that
-        fn = @["parse#type"]
-        return fn if typeof fn is 'function'
-        
-        # Handle compound/optional types
-        # XXX: handle 'or' by returning an array of parsers?
-        type = _ String(type).toLowerCase()
-        for t of <[ Integer Float Number Boolean Object Array Function ]>
-            if type.startsWith t.toLowerCase()
-                return @["parse#t"]
-        @defaultParser or @parseString
-    
-    getParserFromExample: (v) ->
-        return null unless v?
-        type = typeof v
-        
-        if type is not 'object'
-            @getParser type
-        else if _.isArray v
-            @getParser 'Array'
-        else
-            @getParser 'Object'
-    
-
-
-
-/**
- * @class Basic model which mixes in the ParserMixin.
- * @extends BaseModel
- * @borrows ParserMixin
- */
-ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin.mix do
-    constructor: function ParsingModel then BaseModel ...
-
-
-/**
- * @class Basic collection which mixes in the ParserMixin.
- * @extends BaseList
- * @borrows ParserMixin
- */
-ParsingList = exports.ParsingList = BaseList.extend ParserMixin.mix do
-    constructor: function ParsingList then BaseList ...
-
-
-/**
- * @class Basic view which mixes in the ParserMixin.
- * @extends BaseView
- * @borrows ParserMixin
- */
-ParsingView = exports.ParsingView = BaseView.extend ParserMixin.mix do
-    constructor: function ParsingView then BaseView ...
-
-
index 920505f..f51e45a 100644 (file)
@@ -1 +1 @@
-module.exports = exports = '9c55933';
+module.exports = exports = '6e6fd02';
index 79f89e5..323c837 100644 (file)
@@ -3,7 +3,7 @@
   display: inline-block;
   vertical-align: text-top;
   background-repeat: no-repeat;
-  background-image: url("/img/hicons/hicons-sprite-black.png");
+  background-image: url("../img/hicons/hicons-sprite-black.png");
   width: 32px;
   height: 32px;
   line-height: 32px;
@@ -12,7 +12,7 @@
 }
 [class^="hicon-"][class~="icon-white"],
 [class*=" hicon-"][class~="icon-white"] {
-  background-image: url("/img/hicons/hicons-sprite-white.png");
+  background-image: url("../img/hicons/hicons-sprite-white.png");
 }
 [class^="hicon-"][class~="icon-sm"],
 [class*=" hicon-"][class~="icon-sm"] {
index bcdb734..cb6da3c 100644 (file)
@@ -5,10 +5,10 @@
     display inline-block
     vertical-align text-top
     background-repeat no-repeat
-    background-image url("/img/hicons/hicons-sprite-black.png")
+    background-image url("../img/hicons/hicons-sprite-black.png")
     
     &[class~="icon-white"]
-        background-image url("/img/hicons/hicons-sprite-white.png")
+        background-image url("../img/hicons/hicons-sprite-white.png")
     
     // Large-size (32px) icons have no suffix
     width  32px
index 62aa8ba..7831353 100644 (file)
@@ -159,7 +159,7 @@ footer h4 a:active {
   height: 17px;
   line-height: 16px;
   background: transparent no-repeat 0 0;
-  background-image: url("/img/wmf_logo/wmf_logo-white-16x16.png") !important;
+  background-image: url("../img/wmf_logo/wmf_logo-white-16x16.png") !important;
 }
 .image {
   display: block;
@@ -172,7 +172,7 @@ footer h4 a:active {
   width: 45px;
   height: 45px;
   line-height: 45px;
-  background-image: url("/img/wmf_logo/wmf_logo-black-45x45-a100.png");
+  background-image: url("../img/wmf_logo/wmf_logo-black-45x45-a100.png");
   opacity: 0.1;
   filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=10);
 }
@@ -184,7 +184,7 @@ footer h4 a:active {
   width: 31px;
   height: 32px;
   line-height: 32px;
-  background-image: url("/img/public_domain/public_domain-31x32-a100.png");
+  background-image: url("../img/public_domain/public_domain-31x32-a100.png");
   opacity: 0.12;
   filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=12);
 }
index 742deb5..9b92ee3 100644 (file)
@@ -12,7 +12,7 @@
     height 17px
     line-height 16px
     background transparent no-repeat 0 0
-    background-image url("/img/wmf_logo/wmf_logo-white-16x16.png") !important
+    background-image url("../img/wmf_logo/wmf_logo-white-16x16.png") !important
 
 
 .image
@@ -30,7 +30,7 @@
         width 45px
         height 45px
         line-height 45px
-        background-image url("/img/wmf_logo/wmf_logo-black-45x45-a100.png")
+        background-image url("../img/wmf_logo/wmf_logo-black-45x45-a100.png")
         opacity 0.10
         &:hover
             opacity 0.25
@@ -39,7 +39,7 @@
         width 31px
         height 32px
         line-height 32px
-        background-image url("/img/public_domain/public_domain-31x32-a100.png")
+        background-image url("../img/public_domain/public_domain-31x32-a100.png")
         opacity 0.12
         &:hover
             opacity 0.25
diff --git a/www/footer.jade b/www/footer.jade
new file mode 100644 (file)
index 0000000..d34150e
--- /dev/null
@@ -0,0 +1,87 @@
+.info-row.row-fluid
+    .site-map.col.span4
+        h3
+            i.hicon-chart-curve.icon-white.icon-sm
+            a(href="/") Limn
+        ul.site-level
+            li
+                i.icon-home.icon-white
+                a(href="/") Home
+            //- FIXME: put link to /graphs only if dev env, for now.
+            if IS_DEV
+                li
+                    i.icon-th.icon-white
+                    a(href='/graphs') Browse
+            li
+                i.icon-glass.icon-white
+                a(href="/about") About
+                ul
+                    li: a(href="/contact") Contact
+                    li: a(href="http://mediawiki.org/wiki/Analytics", target="_blank") Other Projects
+            li
+                i.icon-bookmark.icon-white
+                a(href="/project") Project
+                //- 
+                    ul
+                        li: a(href="mailto:dsc@wikimedia.org?subject=Reportcard/Limn Feedback") Feedback!
+                        li: a(href="/blog") Blog
+                        li: a(href="/docs/roadmap") Roadmap
+                        li: a(href="https://github.com/wikimedia/limn", target="_blank") Source Repository
+                        li: a(href="https://github.com/wikimedia/limn/issues", target="_blank") Issue Tracker
+                        li: a(href="https://lists.wikimedia.org/mailman/listinfo/analytics", target="_blank") Mailing List
+            //- 
+                li
+                    i.icon-question-sign.icon-white
+                    a(href="/help") Help
+    
+    .get-involved.col.span4
+        h3
+            i.icon-comment.icon-white
+            a(href="/project") Get Involved!
+        p
+            a(href="/project") Limn
+            |  is  
+            a(href="https://github.com/wikimedia/limn", target="_blank") open-source software
+            | , made with love by the 
+            a(href="/about") Wikimedia Analytics Team
+            | .
+        p
+            | Find a bug or have a suggestion? 
+            a(href="mailto:dsc@wikimedia.org?subject=Limn Feedback") We'd love to hear from you!
+        
+    .about-wmf.col.span4
+        h3
+            i.icon-wmf.icon-white
+            a(href="http://wikimedia.org", target="_blank") The Wikimedia Movement
+        p
+            a(href="http://wikimediafoundation.org/wiki/Wikimedia:About", target="_blank") The Wikimedia Foundation
+            |  is the non-profit organization that operates 
+            a(href="http://wikipedia.org", target="_blank") Wikipedia
+            |  and  
+            a(href="http://wikimedia.org", target="_blank") other free knowledge projects
+            | .
+        p
+            | If you're excited about community analytics, check out some of the 
+            a(href="http://stats.wikimedia.org", target="_blank") other stuff we're working on
+            | .
+
+.copyright-row.row-fluid
+    .copyright.inner(xmlns:dct="http://purl.org/dc/terms/")
+        a.public-domain.image(href="http://creativecommons.org/publicdomain/zero/1.0/", rel="license", title="Public Domain", target="_blank")
+        | To the extent possible under law, 
+        a(href="http://www.wikimediafoundation.org", rel="dct:publisher", title="The Wikimedia Foundation", target="_blank")
+            span(property="dct:title") The Wikimedia Foundation
+        |  has waived all copyright and related or neighboring rights to the 
+        span(property="dct:title") Monthly Report Card data and charts
+        | .
+
+.tos-row.row-fluid
+    a(href="http://wikimediafoundation.org/wiki/Terms_of_use", target="_blank") Terms of Use
+    span.separator &bull;
+    a(href="http://wikimediafoundation.org/wiki/Wikimedia:General_disclaimer", target="_blank") Disclaimers
+    span.separator &bull;
+    a(href="http://wikimediafoundation.org/wiki/Wikimedia:Privacy_policy", target="_blank") Privacy Policy
+
+a.wmf-logo.image(href="http://mediawiki.org/wiki/Analytics", target="_blank",
+        title="A product of Team Analytics at the Wikimedia Foundation")
+    | A product of Team Analytics at the Wikimedia Foundation
index ddcbd2b..d89645c 100644 (file)
@@ -7,7 +7,7 @@ append styles
     mixin css('geo-display.css')
 
 block main-scripts
-    script(src="/js/limn/main-geo.js?"+version)
+    script(src=mount+"/js/limn/main-geo.js?"+version)
 
 block content
     section.geo
index a307357..a7e58a3 100644 (file)
@@ -31,108 +31,24 @@ html(lang="en", dir="ltr")
             
             footer
                 block footer
-                    .info-row.row-fluid
-                        .site-map.col.span4
-                            h3
-                                i.hicon-chart-curve.icon-white.icon-sm
-                                a(href="/") Limn
-                            ul.site-level
-                                li
-                                    i.icon-home.icon-white
-                                    a(href="/") Home
-                                //- FIXME: put link to /graphs only if dev env, for now.
-                                if IS_DEV
-                                    li
-                                        i.icon-th.icon-white
-                                        a(href='/graphs') Browse
-                                li
-                                    i.icon-glass.icon-white
-                                    a(href="/about") About
-                                    ul
-                                        li: a(href="/contact") Contact
-                                        li: a(href="http://mediawiki.org/wiki/Analytics", target="_blank") Other Projects
-                                li
-                                    i.icon-bookmark.icon-white
-                                    a(href="/project") Project
-                                    //- 
-                                        ul
-                                            li: a(href="mailto:dsc@wikimedia.org?subject=Reportcard/Limn Feedback") Feedback!
-                                            li: a(href="/blog") Blog
-                                            li: a(href="/docs/roadmap") Roadmap
-                                            li: a(href="https://github.com/wikimedia/limn", target="_blank") Source Repository
-                                            li: a(href="https://github.com/wikimedia/limn/issues", target="_blank") Issue Tracker
-                                            li: a(href="https://lists.wikimedia.org/mailman/listinfo/analytics", target="_blank") Mailing List
-                                //- 
-                                    li
-                                        i.icon-question-sign.icon-white
-                                        a(href="/help") Help
-                        
-                        .get-involved.col.span4
-                            h3
-                                i.icon-comment.icon-white
-                                a(href="/project") Get Involved!
-                            p
-                                a(href="/project") Limn
-                                |  is  
-                                a(href="https://github.com/wikimedia/limn", target="_blank") open-source software
-                                | , made with love by the 
-                                a(href="/about") Wikimedia Analytics Team
-                                | .
-                            p
-                                | Find a bug or have a suggestion? 
-                                a(href="mailto:dsc@wikimedia.org?subject=Limn Feedback") We'd love to hear from you!
-                            
-                        .about-wmf.col.span4
-                            h3
-                                i.icon-wmf.icon-white
-                                a(href="http://wikimedia.org", target="_blank") The Wikimedia Movement
-                            p
-                                a(href="http://wikimediafoundation.org/wiki/Wikimedia:About", target="_blank") The Wikimedia Foundation
-                                |  is the non-profit organization that operates 
-                                a(href="http://wikipedia.org", target="_blank") Wikipedia
-                                |  and  
-                                a(href="http://wikimedia.org", target="_blank") other free knowledge projects
-                                | .
-                            p
-                                | If you're excited about community analytics, check out some of the 
-                                a(href="http://stats.wikimedia.org", target="_blank") other stuff we're working on
-                                | .
-                    
-                    .copyright-row.row-fluid
-                        .copyright.inner(xmlns:dct="http://purl.org/dc/terms/")
-                            a.public-domain.image(href="http://creativecommons.org/publicdomain/zero/1.0/", rel="license", title="Public Domain", target="_blank")
-                            | To the extent possible under law, 
-                            a(href="http://www.wikimediafoundation.org", rel="dct:publisher", title="The Wikimedia Foundation", target="_blank")
-                                span(property="dct:title") The Wikimedia Foundation
-                            |  has waived all copyright and related or neighboring rights to the 
-                            span(property="dct:title") Monthly Report Card data and charts
-                            | .
-                    
-                    .tos-row.row-fluid
-                        a(href="http://wikimediafoundation.org/wiki/Terms_of_use", target="_blank") Terms of Use
-                        span.separator &bull;
-                        a(href="http://wikimediafoundation.org/wiki/Wikimedia:General_disclaimer", target="_blank") Disclaimers
-                        span.separator &bull;
-                        a(href="http://wikimediafoundation.org/wiki/Wikimedia:Privacy_policy", target="_blank") Privacy Policy
-                    
-                    a.wmf-logo.image(href="http://mediawiki.org/wiki/Analytics", target="_blank",
-                            title="A product of Team Analytics at the Wikimedia Foundation")
-                        | A product of Team Analytics at the Wikimedia Foundation
+                    include footer
             
             .scripts
                 block scripts
+                    - var limn_config = { 'mount':mount, 'rev':version, 'env':NODE_ENV };
                     script
                         var VERSION = !{ JSON.stringify(version)  };
                         var ENV     = !{ JSON.stringify(NODE_ENV) };
                         var IS_DEV = ENV === 'development', IS_PROD = ENV === 'production';
+                        var limn_config = !{ JSON.stringify(limn_config) };
                     
                     block lib-scripts
                         for src in sources(WWW+'/modules.yaml')
-                            script(src=src+"?"+version)
+                            script(src=path.join(mount, src)+"?"+version)
                     
                     block page-scripts
                         script
-                            var limn = require('limn/limn');
+                            var limn = require('limn/client');
                     block main-scripts
             
             block addenda
index a9ccc24..1850f11 100644 (file)
@@ -1,12 +1,11 @@
-- root = (function(){ return this; })();
-- if (typeof version == 'undefined') root.version = 'HEAD';
-- if (version == null || !version || version === 'HEAD') { version = String(new Date().getTime()); }
+- if (!locals.version) locals.version = 'HEAD';
+- if (version == null || !version || version === 'HEAD') { locals.version = String(new Date().getTime()); }
 
 mixin css(href, media, path_root)
     - media = media || 'screen'
-    - path_root = path_root || '/css'
-    - var ver = ((typeof version != 'undefined' && version) ? '?'+version : '')
-    - href = ((path_root && href.charAt(0) !== '/') ? path_root+'/' : '') + href + ver
+    - path_root = path_root || (href.charAt(0) === '/' ? locals.mount : locals.path.join(locals.mount, 'css'))
+    - var ver = (locals.version ? '?'+locals.version : '')
+    - href = locals.path.join(path_root, href) + ver
     link(type='text/css', rel='stylesheet', media=media, href=href)
 
 
index 8909c72..3abe9b2 100644 (file)
@@ -99,6 +99,7 @@ development:
                 - data-binding
                 - model-cache
                 - cascading-model
+                - parser-mixin
                 - index
             - graph:
                 - graph-model
@@ -139,7 +140,7 @@ development:
                 - dashboard-model
                 - dashboard-view
                 - index
-            - limn
+            - client
 
 # -   suffix: .js
 #     paths: