Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin system for nengo_gui #809

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ target/
# Vagrant
.vagrant
Vagrantfile

# Node JS stuff
node_modules/
16 changes: 15 additions & 1 deletion nengo_gui/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import inspect
import logging
from pkg_resources import iter_entry_points

import nengo

import nengo_gui.components


logger = logging.getLogger(__name__)


def make_param(name, default):
try:
# the most recent way of making Parameter objects
Expand Down Expand Up @@ -32,7 +38,15 @@ def __init__(self):
make_param(name='has_layout',
default=False))

for clsname, cls in inspect.getmembers(nengo_gui.components):
external_components = {
ep.name: ep.load()
for ep in iter_entry_points(group='nengo_gui.components')}
logger.info(
'Components added to config: %s', external_components.keys())

components = inspect.getmembers(nengo_gui.components)
components += external_components.items()
for clsname, cls in components:
if inspect.isclass(cls):
if issubclass(cls, nengo_gui.components.component.Component):
if cls != nengo_gui.components.component.Component:
Expand Down
27 changes: 25 additions & 2 deletions nengo_gui/guibackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import os
import os.path
import pkgutil
from pkg_resources import iter_entry_points, safe_name
import posixpath
try:
from urllib.parse import unquote
except ImportError: # Python 2.7
Expand Down Expand Up @@ -98,6 +100,7 @@ class GuiRequestHandler(server.HttpWsRequestHandler):
'/': 'serve_main',
'/login': 'login_page',
'/static': 'serve_static',
'/plugin': 'serve_plugin',
'/browse': 'browse',
'/favicon.ico': 'serve_favicon',
}
Expand Down Expand Up @@ -147,12 +150,20 @@ def serve_static(self):
data = pkgutil.get_data('nengo_gui', fn)
return server.HttpResponse(data, mimetype)

@RequireAuthentication('/login')
def serve_plugin(self):
"""Routes request to plugin."""
res = posixpath.relpath(self.resource, '/plugin')
dist_name, plugin_name, path = res.split('/', 2)
plugin = self.server.plugins[safe_name(dist_name) + '/' + plugin_name]
return plugin.serve('/' + path)

@RequireAuthentication('/login')
def browse(self):
r = [b'<ul class="jqueryFileTree" style="display: none;">']
d = unquote(self.db['dir'])
ex_tag = '//examples//'
ex_html = b'<em>built-in examples</em>'
ex_html = b'built-in examples'
if d == '.':
r.append(b'<li class="directory collapsed examples_dir">'
b'<a href="#" rel="' + ex_tag.encode('utf-8') + b'">' +
Expand Down Expand Up @@ -197,7 +208,11 @@ def serve_main(self):

# fill in the javascript needed and return the complete page
components = page.create_javascript()
data = (html % dict(components=components)).encode('utf-8')
plugins = '\r\n'.join(
''.join(str(a) for a in p.get_assets())
for p in self.server.plugins.values())
data = (html % dict(components=components, plugins=plugins)).encode(
'utf-8')
return server.HttpResponse(data)

def serve_favicon(self):
Expand Down Expand Up @@ -371,6 +386,14 @@ def __init__(

self.sessions = SessionManager(self.settings.session_duration)

# load plugins
self.plugins = {
ep.dist.project_name + '/' + ep.name:
ep.load()(ep.name, ep.module_name)
for ep in iter_entry_points(group='nengo_gui.plugins')
}
logger.info('Plugins loaded: %s', self.plugins.keys())

# the list of running Pages
self.pages = []

Expand Down
63 changes: 63 additions & 0 deletions nengo_gui/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import mimetypes
import pkgutil
try:
from html import escape
except ImportError:
from cgi import escape

from nengo_gui import server


class Asset(object):
def __init__(self, tag, inner=None, **attrs):
self.tag = tag
self.inner = inner
self.attrs = attrs

def __str__(self):
attrs_strings = [
'{name}="{value}"'.format(name=k, value=escape(v, quote=True))
for k, v in self.attrs.items() if v is not None]
if self.inner is None:
return '<{tag} {attrs} />'.format(
tag=self.tag, attrs=' '.join(attrs_strings))
else:
return '<{tag} {attrs}>{inner}</{tag}>'.format(
tag=self.tag, attrs=' '.join(attrs_strings), inner=self.inner)


class ScriptAsset(Asset):
def __init__(self, src=None, inner='', type='text/javascript', **attrs):
if src is None and inner is '':
raise ValueError("Either src or inner has to be set.")
elif src is not None and inner is not '':
raise ValueError("Only one of src and inner may be set.")

super(ScriptAsset, self).__init__(
'script', src=src, inner=inner, type=type, **attrs)


class LinkAsset(Asset):
def __init__(self, href, rel='stylesheet', type='text/css', **attrs):
super(LinkAsset, self).__init__(
'link', href=href, rel=rel, type=type, **attrs)


class Plugin(object):
def __init__(self, name, module_name):
self.name = name
self.module_name = module_name

def serve(self, resource):
if resource.startswith('/static/'):
return self.serve_static(resource)
else:
raise server.InvalidResource(resource)

def serve_static(self, resource):
mimetype, _ = mimetypes.guess_type(resource)
data = pkgutil.get_data(self.module_name, resource)
return server.HttpResponse(data, mimetype)

def get_assets(self):
return []
2 changes: 1 addition & 1 deletion nengo_gui/static/ace.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Nengo.disable_editor = function() {
}

Nengo.Ace = function (uid, args) {
this.AceRange = ace.require('ace/range').Range;
this.AceRange = ace.Range;
if (uid[0] === '<') {
console.log("invalid uid for Ace: " + uid);
}
Expand Down
5 changes: 1 addition & 4 deletions nengo_gui/static/components/netgraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,7 @@ Nengo.NetGraph = function(parent, args) {
* this is needed for saving the SVG plot to disk*/

/** load contents of the CSS file as string */
var file = document.getElementById('netgraphcss');
var css = Array.prototype.map.call(file.sheet.cssRules, function
css_text(x) {return x.cssText; } ).join('\n');

var css = require('!!css-loader!./netgraph.css').toString();
/** embed CSS code into SVG tag */
var s = document.createElement('style');
s.setAttribute('type', 'text/css');
Expand Down
7 changes: 7 additions & 0 deletions nengo_gui/static/components/netgraph_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) {
};
};

Nengo.NetGraphItem.prototype.menu_extensions = []

Nengo.NetGraphItem.prototype.set_label = function(label) {
this.label.innerHTML = label;
}
Expand Down Expand Up @@ -408,7 +410,12 @@ Nengo.NetGraphItem.prototype.generate_menu = function () {
function() {self.create_graph('Pointer', self.sp_targets[0]);}]);
items.push(['Semantic pointer plot',
function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]);

}
for (fn in this.menu_extensions) {
items = items.concat(this.menu_extensions[fn](this));
}

// TODO: Enable input and output value plots for basal ganglia network
items.push(['Details ...', function() {self.create_modal();}]);
return items;
Expand Down
2 changes: 2 additions & 0 deletions nengo_gui/static/data_to_csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ var data_to_csv = function(data_set){
return csv_string;

}

module.exports = data_to_csv;
Binary file added nengo_gui/static/dist/favicon.ico
Binary file not shown.
Loading