xoslib wip
diff --git a/planetstack/core/xoslib/README b/planetstack/core/xoslib/README
new file mode 100644
index 0000000..a5af121
--- /dev/null
+++ b/planetstack/core/xoslib/README
@@ -0,0 +1,7 @@
+Add to the following in settings.py
+
+    STATICFILES_DIRS=
+	"/opt/planetstack/core/xoslib/static/",
+
+    TEMPLATE_DIRS=
+        "/opt/planetstack/xoslib/templates",
\ No newline at end of file
diff --git a/planetstack/core/xoslib/dashboards/sliverListTest.html b/planetstack/core/xoslib/dashboards/sliverListTest.html
new file mode 100644
index 0000000..2238181
--- /dev/null
+++ b/planetstack/core/xoslib/dashboards/sliverListTest.html
@@ -0,0 +1,26 @@
+{% load mustache %}
+{% load straight_include %}
+
+<script src="{{ STATIC_URL }}/js/underscore-min.js"></script>
+<script src="{{ STATIC_URL }}/js/backbone-min.js"></script>
+<script src="{{ STATIC_URL }}/js/backbone-tastypie.js"></script>
+<script src="{{ STATIC_URL }}/js/ICanHaz.min.js"></script>
+
+<script src="{{ STATIC_URL }}/js/xos-backbone.js"></script>
+<script src="{{ STATIC_URL }}/js/sliverListTest.js"></script>
+
+<script type="text/html" id="sliverTemplate">
+  {% straight_include "mustache/sliverTemplate.mustache" %}
+</script>
+
+<script type="text/html" id="listApp">
+  {% straight_include "mustache/listApp.mustache" %}
+</script>
+
+<script type="text/html" id="detailApp">
+  {% straight_include "mustache/detailApp.mustache" %}
+</script>
+
+<div id="app">
+  {% mustache "mustache/listApp" %}
+</div>
diff --git a/planetstack/core/xoslib/static/js/sliverListTest.js b/planetstack/core/xoslib/static/js/sliverListTest.js
new file mode 100644
index 0000000..294ffd3
--- /dev/null
+++ b/planetstack/core/xoslib/static/js/sliverListTest.js
@@ -0,0 +1,177 @@
+(function(){
+
+window.SliverView = Backbone.View.extend({
+    tagName: 'li',
+    className: 'XOSLib.sliver',
+
+    events: {
+        'click .permalink': 'navigate'
+    },
+
+    initialize: function(){
+        this.model.bind('change', this.render, this);
+    },
+
+    navigate: function(e){
+        this.trigger('navigate', this.model);
+        e.preventDefault();
+    },
+
+    render: function(){
+        $(this.el).html(ich.sliverTemplate(this.model.toJSON()));
+        return this;
+    }
+});
+
+
+window.DetailApp = Backbone.View.extend({
+    events: {
+        'click .home': 'home'
+    },
+
+    home: function(e){
+        this.trigger('home');
+        e.preventDefault();
+    },
+
+    render: function(){
+        $(this.el).html(ich.detailApp(this.model.toJSON()));
+        return this;
+    }
+});
+
+window.InputView = Backbone.View.extend({
+    events: {
+        'click .sliver': 'createSliver',
+        'keypress #message': 'createOnEnter'
+    },
+
+    createOnEnter: function(e){
+        if((e.keyCode || e.which) == 13){
+            this.createSliver();
+            e.preventDefault();
+        }
+
+    },
+
+    createSliver: function(){
+        var message = this.$('#message').val();
+        if(message){
+            this.collection.create({
+                message: message
+            });
+            this.$('#message').val('');
+        }
+    }
+
+});
+
+window.ListView = Backbone.View.extend({
+    initialize: function(){
+        _.bindAll(this, 'addOne', 'addAll');
+
+        this.collection.bind('add', this.addOne);
+        this.collection.bind('reset', this.addAll, this);
+        this.views = [];
+    },
+
+    addAll: function(){
+        this.views = [];
+        this.collection.each(this.addOne);
+    },
+
+    addOne: function(sliver){
+        var view = new SliverView({
+            model: XOSLib.sliver
+        });
+        $(this.el).prepend(view.render().el);
+        this.views.push(view);
+        view.bind('all', this.rethrow, this);
+    },
+
+    rethrow: function(){
+        this.trigger.apply(this, arguments);
+    }
+
+});
+
+window.ListApp = Backbone.View.extend({
+    el: "#app",
+
+    rethrow: function(){
+        this.trigger.apply(this, arguments);
+    },
+
+    render: function(){
+        console.log("listApp.render");
+        $(this.el).html(ich.listApp({}));
+        var list = new ListView({
+            collection: this.collection,
+            el: this.$('#slivers')
+        });
+        list.addAll();
+        list.bind('all', this.rethrow, this);
+        new InputView({
+            collection: this.collection,
+            el: this.$('#input')
+        });
+    }
+});
+
+
+window.Router = Backbone.Router.extend({
+    routes: {
+        '': 'list',
+        ':id/': 'detail'
+    },
+
+    navigate_to: function(model){
+        var path = (model && model.get('id') + '/') || '';
+        console.log("Router.navigate_to");
+        this.navigate(path, true);
+    },
+
+    detail: function(){ console.log("Router.detail"); },
+
+    list: function(){ console.log("Router.list"); }
+});
+
+$(function(){
+    window.app = window.app || {};
+    app.router = new Router();
+    app.slivers = new XOSLib.slivers();
+    app.list = new ListApp({
+        el: $("#app"),
+        collection: app.slivers
+    });
+    app.detail = new DetailApp({
+        el: $("#app")
+    });
+    app.router.bind('route:list', function(){
+        console.log("Router:list2");
+        app.slivers.maybeFetch({
+            success: _.bind(app.list.render, app.list)
+        });
+    });
+    app.router.bind('route:detail', function(id){
+        console.log("Router:detail2");
+        app.slivers.getOrFetch(app.slivers.urlRoot + id + '/', {
+            success: function(model){
+                app.detail.model = model;
+                app.detail.render();
+            }
+        });
+    });
+
+    app.slivers.maybeFetch({
+        success: _.bind(app.list.render, app.list)
+    });
+
+    app.list.bind('navigate', app.router.navigate_to, app.router);
+    app.detail.bind('home', app.router.navigate_to, app.router);
+    Backbone.history.start({
+        pushState: true,
+        silent: app.loaded
+    });
+});
+})();
diff --git a/planetstack/core/xoslib/static/js/xos-backbone.js b/planetstack/core/xoslib/static/js/xos-backbone.js
new file mode 100644
index 0000000..99f0784
--- /dev/null
+++ b/planetstack/core/xoslib/static/js/xos-backbone.js
@@ -0,0 +1,49 @@
+SLIVER_API = "/plstackapi/slivers/";
+
+XOSCollection = Backbone.Collection.extend({
+    maybeFetch: function(options){
+            // Helper function to fetch only if this collection has not been fetched before.
+        if(this._fetched){
+                // If this has already been fetched, call the success, if it exists
+            options.success && options.success();
+            console.log("alreadyFetched");
+            return;
+        }
+
+            // when the original success function completes mark this collection as fetched
+        var self = this,
+        successWrapper = function(success){
+            return function(){
+                self._fetched = true;
+                success && success.apply(this, arguments);
+            };
+        };
+        options.success = successWrapper(options.success);
+        console.log("call fetch");
+        this.fetch(options);
+    },
+
+    getOrFetch: function(id, options){
+            // Helper function to use this collection as a cache for models on the server
+        var model = this.get(id);
+
+        if(model){
+            options.success && options.success(model);
+            return;
+        }
+
+        model = new Sliver({
+            resource_uri: id
+        });
+
+        model.fetch(options);
+    }
+});
+
+function xoslib() {
+    this.sliver = Backbone.Model.extend({ urlRoot: SLIVER_API });
+    this.slivers = XOSCollection.extend({ urlRoot: SLIVER_API,
+                                    model: this.sliver});
+};
+
+XOSLib = new xoslib();
diff --git a/planetstack/core/xoslib/templates/mustache/detailApp.mustache b/planetstack/core/xoslib/templates/mustache/detailApp.mustache
new file mode 100644
index 0000000..e415945
--- /dev/null
+++ b/planetstack/core/xoslib/templates/mustache/detailApp.mustache
@@ -0,0 +1,8 @@
+<h2>
+  <a class="home" href="/">All Slivers</a>
+</h2>
+<ul id="slivers">
+  <li class="sliver"
+      {{>sliverTemplate}}
+  </li>
+</ul>
diff --git a/planetstack/core/xoslib/templates/mustache/listApp.mustache b/planetstack/core/xoslib/templates/mustache/listApp.mustache
new file mode 100644
index 0000000..3af7ebe
--- /dev/null
+++ b/planetstack/core/xoslib/templates/mustache/listApp.mustache
@@ -0,0 +1,3 @@
+<h2>All Slivers</h2>
+<ul id="slivers">
+</ul>
diff --git a/planetstack/core/xoslib/templates/mustache/sliverTemplate.mustache b/planetstack/core/xoslib/templates/mustache/sliverTemplate.mustache
new file mode 100644
index 0000000..9959a19
--- /dev/null
+++ b/planetstack/core/xoslib/templates/mustache/sliverTemplate.mustache
@@ -0,0 +1 @@
+<a class="permalink" href="/{{id}}/">{{ name }}</a>
diff --git a/planetstack/core/xoslib/templatetags/mustache.py b/planetstack/core/xoslib/templatetags/mustache.py
new file mode 100644
index 0000000..a3b3b2a
--- /dev/null
+++ b/planetstack/core/xoslib/templatetags/mustache.py
@@ -0,0 +1,51 @@
+from django import template
+from django.conf import settings
+import pystache
+import os
+
+register = template.Library()
+
+class View(pystache.View):
+    template_path = settings.TEMPLATE_DIRS[0]
+
+    def __init__(self, template_dir, template_name, context):
+        self.template_path = template_dir
+        self.template_name = template_name
+        return super(View, self).__init__(context=context)
+
+class MustacheNode(template.Node):
+    def __init__(self, template_name, attr=None):
+        for template_dir in settings.TEMPLATE_DIRS:
+            if os.path.exists(os.path.join(template_dir, template_name) + ".mustache"):
+                break
+        else:
+            raise IOError("failed to find %s in %s" % (template_name, str(settings.TEMPLATE_DIRS)))
+
+        self.template_path = template_dir
+        self.template = template_name
+        self.attr = attr
+
+    def render(self, context):
+        mcontext = context[self.attr] if self.attr else {}
+        view = View(self.template_path, self.template, context=mcontext)
+        return view.render()
+
+def do_mustache(parser, token):
+    """
+    Loads a mustache template and render it inline
+    
+    Example::
+    
+    {% mustache "foo/bar" data %}
+    
+    """
+    bits = token.split_contents()
+    if len(bits) not in  [2,3]:
+        raise template.TemplateSyntaxError("%r tag takes two arguments: the location of the template file, and the template context" % bits[0])
+    path = bits[1]
+    path = path[1:-1]
+    attrs = bits[2:]
+    return MustacheNode(path, *attrs)
+
+
+register.tag("mustache", do_mustache)
diff --git a/planetstack/core/xoslib/templatetags/straight_include.py b/planetstack/core/xoslib/templatetags/straight_include.py
new file mode 100644
index 0000000..54710f3
--- /dev/null
+++ b/planetstack/core/xoslib/templatetags/straight_include.py
@@ -0,0 +1,62 @@
+"""
+Straight Include template tag by @HenrikJoreteg
+
+Django templates don't give us any way to escape template tags.
+
+So if you ever need to include client side templates for ICanHaz.js (or anything else that
+may confuse django's templating engine) You can is this little snippet.
+
+Just use it as you would a normal {% include %} tag. It just won't process the included text.
+
+It assumes your included templates are in you django templates directory.
+
+Usage:
+
+{% load straight_include %}
+
+{% straight_include "my_icanhaz_templates.html" %}
+
+"""
+
+import os
+from django import template
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class StraightIncludeNode(template.Node):
+    def __init__(self, template_path):
+        for template_dir in settings.TEMPLATE_DIRS:
+            self.filepath = '%s/%s' % (template_dir, template_path)
+            if os.path.exists(self.filepath):
+                break
+        else:
+            raise IOError("cannot find %s in %s" % (template_path, str(TEMPLATE_DIRS)))
+
+    def render(self, context):
+        fp = open(self.filepath, 'r')
+        output = fp.read()
+        fp.close()
+        return output
+
+
+def do_straight_include(parser, token):
+    """
+    Loads a template and includes it without processing it
+    
+    Example::
+    
+    {% straight_include "foo/some_include" %}
+    
+    """
+    bits = token.split_contents()
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError("%r tag takes one argument: the location of the file within the template folder" % bits[0])
+    path = bits[1][1:-1]
+    
+    return StraightIncludeNode(path)
+
+
+register.tag("straight_include", do_straight_include)
diff --git a/planetstack/core/xoslib/up.sh b/planetstack/core/xoslib/up.sh
new file mode 100755
index 0000000..1121892
--- /dev/null
+++ b/planetstack/core/xoslib/up.sh
@@ -0,0 +1,3 @@
+scp static/js/*.js princeton_planetstack@node43.princeton.vicci.org:/opt/planetstack/core/xoslib/static/js/
+scp templates/mustache/*.mustache princeton_planetstack@node43.princeton.vicci.org:/opt/planetstack/core/xoslib/templates/mustache/
+scp dashboards/*.html princeton_planetstack@node43.princeton.vicci.org:/opt/planetstack/templates/admin/dashboard/