{"action":"created","comment":{"url":"https://api.github.com/repos/nengo/nengo_gui/pulls/comments/33971766","id":33971766,"diff_hunk":"@@ -0,0 +1,436 @@\n+import importlib\n+import json\n+import logging\n+import os\n+import socket\n+import threading\n+import time\n+import traceback\n+\n+import nengo\n+\n+import nengo_gui\n+import nengo_gui.components.action\n+import nengo_gui.config\n+\n+\n+class Sim(object):\n+    \"\"\"A single Simulator attached to an html visualization.\n+\n+    Parameters\n+    ----------\n+\n+    sim_server : nengo_gui.SimServer\n+        The master SimServer\n+    filename : str\n+        The filename to open.  If this is the same as sim_server.filename\n+        then it will use the existing sim_server.model and sim_server.locals\n+        (if available).  Otherwise, the file will be executed to generate\n+        the model\n+    reset_cfg : bool, optional\n+        If True, the existing .cfg file will be erased\n+    \"\"\"\n+\n+    # Some Simulators can only have one instance running at a time\n+    singleton_sims = dict(nengo_spinnaker=None)\n+\n+    def __init__(self, sim_server, filename, reset_cfg=False):\n+        self.sim_server = sim_server\n+        self.backend = sim_server.backend\n+\n+        self.code = None     # the code for the model\n+        self.model = None    # the nengo.Network\n+        self.locals = None   # the locals() dictionary after executing\n+\n+        self.changed = False   # has the model been changed?\n+        self.new_code = None   # new code that should be re-executed\n+        self.paused = False    # is the simulation paused\n+        self.finished = False  # should this Sim be shut down\n+        self._sim = None       # the current nengo.Simulator\n+        self.rebuild = False   # should the model be rebuilt\n+\n+        self.undo_stack = []\n+        self.redo_stack = []\n+\n+        self.config_save_period = 2.0  # minimum time between saves\n+\n+        self.lock = threading.Lock()\n+\n+        # use the default filename if none is given\n+        if filename is None:\n+            self.filename = sim_server.filename\n+        else:\n+            self.filename = os.path.relpath(filename)\n+\n+        # determine the .cfg filename\n+        if sim_server.filename_cfg is None:\n+            self.filename_cfg = self.filename + '.cfg'\n+        else:\n+            self.filename_cfg = sim_server.filename_cfg\n+\n+        if reset_cfg:\n+            self.clear_config()\n+\n+        self.load()\n+\n+        self.net_graph = self.get_net_graph()\n+\n+        # build and run the model in a separate thread\n+        t = threading.Thread(target=self.runner)\n+        t.daemon = True\n+        t.start()\n+\n+    @property\n+    def sim(self):\n+        return self._sim\n+\n+    @sim.setter\n+    def sim(self, value):\n+        if hasattr(self._sim, 'close'):\n+            self._sim.close()\n+        self._sim = value\n+\n+    def get_net_graph(self):\n+        for c in self.components:\n+            if isinstance(c, nengo_gui.components.NetGraph):\n+                return c\n+        return None\n+\n+    def clear_config(self):\n+        if os.path.isfile(self.filename_cfg):\n+            os.remove(self.filename_cfg)\n+\n+    def load(self):\n+        \"\"\"Load the model and initialize everything\"\"\"\n+        if self.filename == self.sim_server.filename:\n+            # if we're on the default filenaem, just load it from the SimServer\n+            self.model = self.sim_server.model\n+            if self.sim_server.locals is None:\n+                self.locals = None\n+            else:\n+                self.locals = self.sim_server.locals.copy()\n+        else:\n+            self.model = None\n+            self.locals = None\n+\n+        # if we still don't have a locals dictionary, then run the script\n+        if self.locals is None:\n+            try:\n+                with open(self.filename) as f:\n+                    code = f.read()\n+            except IOError:\n+                code = ''\n+\n+            self.execute(code)\n+\n+        if self.model is None:\n+            self.model = nengo.Network()\n+            self.locals['model'] = self.model\n+\n+        # figure out good names for objects\n+        self.name_finder = nengo_gui.NameFinder(self.locals, self.model)\n+        self.default_labels = self.name_finder.known_name\n+\n+        # load the .cfg file\n+        self.config = self.load_config()\n+        self.config_save_needed = False\n+        self.config_save_time = None   # time of last config file save\n+\n+        self.uid_prefix_counter = {}\n+\n+        self.create_components()\n+\n+    def create_components(self):\n+        \"\"\"Generate the actual Components from the Templates\"\"\"\n+        self.components = []\n+        self.template_uids = {}\n+        for k, v in self.locals.items():\n+            if isinstance(v, nengo_gui.components.component.Template):\n+                self.template_uids[v] = k\n+                c = v.create(self)\n+                self.sim_server.component_uids[c.uid] = c\n+                self.components.append(c)\n+\n+        # this ensures NetGraph, AceEditor, and SimControl are first\n+        self.components.sort(key=lambda x: x.component_order)\n+\n+    def add_template(self, template):\n+        \"\"\"Add a new Component to an existing Sim.\"\"\"\n+        c = template.create(self)\n+        self.sim_server.component_uids[c.uid] = c\n+\n+        self.components.append(c)\n+\n+        return c\n+\n+    def execute(self, code):\n+        \"\"\"Run the given code to generate self.model and self.locals.\n+\n+        The code will be stored in self.code, any output to stdout will\n+        be a string as self.stdout, and any error will be in self.error.\n+        \"\"\"\n+        locals = {}\n+        locals['nengo_gui'] = nengo_gui\n+        locals['__file__'] = self.filename\n+\n+        self.code = code\n+        self.error = None\n+        self.stdout = ''\n+\n+        patch = nengo_gui.monkey.Patch(self.filename)\n+        try:\n+            with patch:\n+                exec(code, locals)\n+        except nengo_gui.monkey.StartedSimulatorException:\n+            # no running a simulator inside a script\n+            pass\n+        except nengo_gui.monkey.StartedVizException:\n+            # no running nengo_gui inside a script\n+            pass\n+        except:\n+            line = nengo_gui.monkey.determine_line_number()\n+            self.error = dict(trace=traceback.format_exc(), line=line)\n+        self.stdout = patch.stdout.getvalue()\n+\n+        # make sure we've defined a nengo.Network\n+        model = locals.get('model', None)\n+        if not isinstance(model, nengo.Network):\n+            if self.error is None:\n+                line = len(code.split('\\n'))\n+                self.error = dict(trace='must declare a nengo.Network '\n+                                  'called \"model\"', line=line)\n+            model = None\n+\n+        self.model = model\n+        self.locals = locals\n+\n+    def load_config(self):\n+        \"\"\"Load the .cfg file\"\"\"\n+        config = nengo_gui.config.Config()\n+        self.locals['nengo_gui'] = nengo_gui\n+        self.locals['_viz_config'] = config\n+        fname = self.filename_cfg\n+        if os.path.exists(fname):\n+            with open(fname) as f:\n+                config_code = f.readlines()\n+            for line in config_code:\n+                try:\n+                    exec(line, self.locals)\n+                except Exception as e:\n+                    if self.sim_server.interactive:\n+                        logging.debug('error parsing config: %s' % line)\n+\n+        # make sure the required Components exist\n+        if '_viz_sim_control' not in self.locals:\n+            template = nengo_gui.components.SimControlTemplate()\n+            self.locals['_viz_sim_control'] = template\n+        if '_viz_net_graph' not in self.locals:\n+            template = nengo_gui.components.NetGraphTemplate()\n+            self.locals['_viz_net_graph'] = template\n+        if '_viz_ace_editor' not in self.locals:\n+            template = nengo_gui.components.AceEditorTemplate()\n+            self.locals['_viz_ace_editor'] = template\n+\n+        if self.model is not None:\n+            if config[self.model].pos is None:\n+                config[self.model].pos = (0, 0)\n+            if config[self.model].size is None:\n+                config[self.model].size = (1.0, 1.0)\n+\n+        for k, v in self.locals.items():\n+            if isinstance(v, nengo_gui.components.component.Template):\n+                self.default_labels[v] = k\n+\n+        return config\n+\n+    def save_config(self, lazy=False, force=False):\n+        \"\"\"Write the .cfg file to disk.\n+\n+        Parameters\n+        ----------\n+        lazy : bool\n+            If True, then only save if it has been more than config_save_time\n+            since the last save and if config_save_needed\n+        force : bool\n+            If True, then always save right now\n+        \"\"\"\n+        if not force and not self.config_save_needed:\n+            return\n+\n+        now_time = time.time()\n+        if not force and lazy and self.config_save_time is not None:\n+            if (now_time - self.config_save_time) < self.config_save_period:\n+                return\n+\n+        with self.lock:\n+            self.config_save_time = now_time\n+            self.config_save_needed = False\n+            try:\n+                with open(self.filename_cfg, 'w') as f:\n+                    f.write(self.config.dumps(uids=self.default_labels))\n+            except IOError:\n+                print(\"Could not save %s; permission denied\" %\n+                      self.filename_cfg)\n+\n+    def modified_config(self):\n+        \"\"\"Set a flag that the config file should be saved.\"\"\"\n+        self.config_save_needed = True\n+\n+    def create_javascript(self):\n+        \"\"\"Generate the javascript for the current model and layout.\"\"\"\n+        if self.filename is not None:\n+            fn = json.dumps(self.filename)\n+            webpage_title_js = ';document.title = %s;' % fn\n+        else:\n+            webpage_title_js = ''\n+\n+        assert isinstance(self.components[0], nengo_gui.components.SimControl)\n+\n+        component_js = '\\n'.join([c.javascript() for c in self.components])\n+        component_js += webpage_title_js\n+        if not self.sim_server.allow_file_change:\n+            component_js += \"$('#Open_file_button').addClass('deactivated');\"\n+            pass\n+        return component_js\n+\n+    def get_label(self, obj, default_labels=None):\n+        \"\"\"Return a readable label for an object.\n+\n+        If the object has a .label set, this will be used.  Otherwise, it\n+        uses default_labels, which thanks to the NameFinder will be legal\n+        Python code for referring to the object given the current locals()\n+        dictionary (\"model.ensembles[1]\" or \"ens\" or \"model.buffer.state\".\n+        If it has to use default_labels, it will only use the last part of the \n+        label (after the last \".\").  This avoids redundancy in nested displays.\n+        \"\"\"\n+        if default_labels is None:\n+            default_labels = self.default_labels\n+        label = obj.label\n+        if label is None:\n+            label = default_labels.get(obj, None)\n+            if label is None:\n+                raise Exception('ERROR finding label: %s' % obj)\n+            else:\n+                if '.' in label:\n+                    label = label.rsplit('.', 1)[1]\n+        if label is None:\n+            label = repr(obj)\n+        return label\n+\n+    def get_uid(self, obj, default_labels=None):\n+        \"\"\"Return a unique identifier for an object.\n+\n+        This should be the value given by the NameFinder.\n+        \"\"\"\n+        if default_labels is None:\n+            default_labels = self.default_labels\n+        uid = default_labels.get(obj, None)\n+        if uid is None:\n+            # TODO: do we ever need to fall back on this case?  This should\n+            # only happen if something goes wrong.\n+            uid = repr(obj)\n+        return uid\n+\n+    def finish(self):\n+        \"\"\"Shut down this simulator.\"\"\"\n+        if not self.finished:\n+            self.finished = True\n+            self.sim_server.remove_sim(self)\n+\n+    def generate_uid(self, obj, prefix):\n+        \"\"\"Make a new unique identifier for an object.\n+\n+        This is used for new Components being created (so they can have\n+        a unique identifier in the .cfg file).\n+        \"\"\"\n+        index = self.uid_prefix_counter.get(prefix, 0)\n+        uid = '%s%d' % (prefix, index)\n+        while uid in self.locals:\n+            index += 1\n+            uid = '%s%d' % (prefix, index)\n+        self.uid_prefix_counter[prefix] = index + 1\n+\n+        self.locals[uid] = obj\n+        self.default_labels[obj] = uid\n+\n+    def remove_uid(self, uid):\n+        \"\"\"Remove a generated uid (for when a component is removed).\"\"\"\n+        if uid in self.locals:\n+            obj = self.locals[uid]\n+            del self.locals[uid]\n+            del self.default_labels[obj]\n+        else:\n+            print 'WARNING: remove_uid called on unknown uid', uid\n+\n+    def remove_component(self, component):\n+        \"\"\"Remove a component from the layout.\"\"\"\n+        del self.sim_server.component_uids[component.uid]\n+        template = component.template\n+        uid = self.get_uid(template)\n+        self.remove_uid(uid)\n+        self.components.remove(component)\n+\n+    def config_change(self, component, new_cfg, old_cfg):\n+        act = nengo_gui.components.action.ConfigAction(self,\n+                                                       component=component,\n+                                                       new_cfg=new_cfg,\n+                                                       old_cfg=old_cfg)\n+        self.undo_stack.append([act])\n+\n+    def remove_graph(self, component):\n+        act = nengo_gui.components.action.RemoveGraph(self.net_graph,\n+                                                      component)\n+        self.undo_stack.append([act])\n+\n+    def build(self):\n+        \"\"\"Build the model.\"\"\"\n+        # use the lock to make sure only one Simulator is building at a time\n+        # TODO: should there be a master lock in the SimServer?\n+        with self.lock:\n+            self.building = True\n+\n+            # modify the model for the various Components\n+            for c in self.components:\n+                c.add_nengo_objects(self)\n+\n+            # determine the backend to use\n+            backend = importlib.import_module(self.backend)\n+            # if only one Simulator is allowed at a time, finish the old one\n+            old_sim = Sim.singleton_sims.get(self.backend, None)\n+            if old_sim is not None:\n+                if old_sim is not self:","path":"nengo_gui/sim.py","position":401,"original_position":401,"commit_id":"3fdc2b12214c637437ef72da4e6a09edf1c4f6bd","original_commit_id":"3fdc2b12214c637437ef72da4e6a09edf1c4f6bd","user":{"login":"jgosmann","id":850157,"avatar_url":"https://avatars.githubusercontent.com/u/850157?v=3","gravatar_id":"","url":"https://api.github.com/users/jgosmann","html_url":"https://github.com/jgosmann","followers_url":"https://api.github.com/users/jgosmann/followers","following_url":"https://api.github.com/users/jgosmann/following{/other_user}","gists_url":"https://api.github.com/users/jgosmann/gists{/gist_id}","starred_url":"https://api.github.com/users/jgosmann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgosmann/subscriptions","organizations_url":"https://api.github.com/users/jgosmann/orgs","repos_url":"https://api.github.com/users/jgosmann/repos","events_url":"https://api.github.com/users/jgosmann/events{/privacy}","received_events_url":"https://api.github.com/users/jgosmann/received_events","type":"User","site_admin":false},"body":"Combining the two if statements with `and` would save one line and an indentation level which would be a minor readability improvement IMO.","created_at":"2015-07-06T19:20:07Z","updated_at":"2015-07-06T19:20:07Z","html_url":"https://github.com/nengo/nengo_gui/pull/460#discussion_r33971766","pull_request_url":"https://api.github.com/repos/nengo/nengo_gui/pulls/460","_links":{"self":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/comments/33971766"},"html":{"href":"https://github.com/nengo/nengo_gui/pull/460#discussion_r33971766"},"pull_request":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/460"}}},"pull_request":{"url":"https://api.github.com/repos/nengo/nengo_gui/pulls/460","id":39202489,"html_url":"https://github.com/nengo/nengo_gui/pull/460","diff_url":"https://github.com/nengo/nengo_gui/pull/460.diff","patch_url":"https://github.com/nengo/nengo_gui/pull/460.patch","issue_url":"https://api.github.com/repos/nengo/nengo_gui/issues/460","number":460,"state":"open","locked":false,"title":"Refactor viz","user":{"login":"tcstewar","id":2380772,"avatar_url":"https://avatars.githubusercontent.com/u/2380772?v=3","gravatar_id":"","url":"https://api.github.com/users/tcstewar","html_url":"https://github.com/tcstewar","followers_url":"https://api.github.com/users/tcstewar/followers","following_url":"https://api.github.com/users/tcstewar/following{/other_user}","gists_url":"https://api.github.com/users/tcstewar/gists{/gist_id}","starred_url":"https://api.github.com/users/tcstewar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tcstewar/subscriptions","organizations_url":"https://api.github.com/users/tcstewar/orgs","repos_url":"https://api.github.com/users/tcstewar/repos","events_url":"https://api.github.com/users/tcstewar/events{/privacy}","received_events_url":"https://api.github.com/users/tcstewar/received_events","type":"User","site_admin":false},"body":"This PR reworks the Viz / VizSim mess.  \r\n\r\nThe most important change is now there is no state shared across simulations.  Previously, the one Viz object held a lot of information that should be different across different tabs open to nengo_gui.  This caused all sorts of threading problems because one tab could change something that was required by another tab.  A similar thing could happen when reloading a page (since that's the same as opening a new tab).\r\n\r\nIn the new approach, there is one SimServer object (the new version of Viz), and the only things it holds are immutable default values.  Every page has its own Sim object (the new version of VizSim).  This is the main workhorse.  It maintains all of its own state and is not dependent on SimServer after initialization.\r\n\r\nThis change also cleaned up the code for executing the script, and now the same code is used for executing the script the first time (on startup) and when code is changed in the text editor.  \r\n\r\nI've also added documentation to the new Sim and SimServer classes.  :)\r\n\r\nI believe that this reorganization should address the dreaded \"pop\" bug.  But it's been difficult to replicate that bug, so it's hard to test.  But in any case this is a *much* cleaner code organization and so as long as I haven't broken any features, I think this is the right way to go.  :)","created_at":"2015-07-04T04:31:54Z","updated_at":"2015-07-06T19:20:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7883b689acd00ce91dc4e41336168ca9eadcec83","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nengo/nengo_gui/pulls/460/commits","review_comments_url":"https://api.github.com/repos/nengo/nengo_gui/pulls/460/comments","review_comment_url":"https://api.github.com/repos/nengo/nengo_gui/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nengo/nengo_gui/issues/460/comments","statuses_url":"https://api.github.com/repos/nengo/nengo_gui/statuses/3fdc2b12214c637437ef72da4e6a09edf1c4f6bd","head":{"label":"nengo:refactor-viz","ref":"refactor-viz","sha":"3fdc2b12214c637437ef72da4e6a09edf1c4f6bd","user":{"login":"nengo","id":9197486,"avatar_url":"https://avatars.githubusercontent.com/u/9197486?v=3","gravatar_id":"","url":"https://api.github.com/users/nengo","html_url":"https://github.com/nengo","followers_url":"https://api.github.com/users/nengo/followers","following_url":"https://api.github.com/users/nengo/following{/other_user}","gists_url":"https://api.github.com/users/nengo/gists{/gist_id}","starred_url":"https://api.github.com/users/nengo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nengo/subscriptions","organizations_url":"https://api.github.com/users/nengo/orgs","repos_url":"https://api.github.com/users/nengo/repos","events_url":"https://api.github.com/users/nengo/events{/privacy}","received_events_url":"https://api.github.com/users/nengo/received_events","type":"Organization","site_admin":false},"repo":{"id":28949681,"name":"nengo_gui","full_name":"nengo/nengo_gui","owner":{"login":"nengo","id":9197486,"avatar_url":"https://avatars.githubusercontent.com/u/9197486?v=3","gravatar_id":"","url":"https://api.github.com/users/nengo","html_url":"https://github.com/nengo","followers_url":"https://api.github.com/users/nengo/followers","following_url":"https://api.github.com/users/nengo/following{/other_user}","gists_url":"https://api.github.com/users/nengo/gists{/gist_id}","starred_url":"https://api.github.com/users/nengo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nengo/subscriptions","organizations_url":"https://api.github.com/users/nengo/orgs","repos_url":"https://api.github.com/users/nengo/repos","events_url":"https://api.github.com/users/nengo/events{/privacy}","received_events_url":"https://api.github.com/users/nengo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nengo/nengo_gui","description":"Nengo interactive visualizer","fork":false,"url":"https://api.github.com/repos/nengo/nengo_gui","forks_url":"https://api.github.com/repos/nengo/nengo_gui/forks","keys_url":"https://api.github.com/repos/nengo/nengo_gui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nengo/nengo_gui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nengo/nengo_gui/teams","hooks_url":"https://api.github.com/repos/nengo/nengo_gui/hooks","issue_events_url":"https://api.github.com/repos/nengo/nengo_gui/issues/events{/number}","events_url":"https://api.github.com/repos/nengo/nengo_gui/events","assignees_url":"https://api.github.com/repos/nengo/nengo_gui/assignees{/user}","branches_url":"https://api.github.com/repos/nengo/nengo_gui/branches{/branch}","tags_url":"https://api.github.com/repos/nengo/nengo_gui/tags","blobs_url":"https://api.github.com/repos/nengo/nengo_gui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nengo/nengo_gui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nengo/nengo_gui/git/refs{/sha}","trees_url":"https://api.github.com/repos/nengo/nengo_gui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nengo/nengo_gui/statuses/{sha}","languages_url":"https://api.github.com/repos/nengo/nengo_gui/languages","stargazers_url":"https://api.github.com/repos/nengo/nengo_gui/stargazers","contributors_url":"https://api.github.com/repos/nengo/nengo_gui/contributors","subscribers_url":"https://api.github.com/repos/nengo/nengo_gui/subscribers","subscription_url":"https://api.github.com/repos/nengo/nengo_gui/subscription","commits_url":"https://api.github.com/repos/nengo/nengo_gui/commits{/sha}","git_commits_url":"https://api.github.com/repos/nengo/nengo_gui/git/commits{/sha}","comments_url":"https://api.github.com/repos/nengo/nengo_gui/comments{/number}","issue_comment_url":"https://api.github.com/repos/nengo/nengo_gui/issues/comments{/number}","contents_url":"https://api.github.com/repos/nengo/nengo_gui/contents/{+path}","compare_url":"https://api.github.com/repos/nengo/nengo_gui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nengo/nengo_gui/merges","archive_url":"https://api.github.com/repos/nengo/nengo_gui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nengo/nengo_gui/downloads","issues_url":"https://api.github.com/repos/nengo/nengo_gui/issues{/number}","pulls_url":"https://api.github.com/repos/nengo/nengo_gui/pulls{/number}","milestones_url":"https://api.github.com/repos/nengo/nengo_gui/milestones{/number}","notifications_url":"https://api.github.com/repos/nengo/nengo_gui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nengo/nengo_gui/labels{/name}","releases_url":"https://api.github.com/repos/nengo/nengo_gui/releases{/id}","created_at":"2015-01-08T05:00:06Z","updated_at":"2015-06-09T16:18:58Z","pushed_at":"2015-07-06T19:15:56Z","git_url":"git://github.com/nengo/nengo_gui.git","ssh_url":"git@github.com:nengo/nengo_gui.git","clone_url":"https://github.com/nengo/nengo_gui.git","svn_url":"https://github.com/nengo/nengo_gui","homepage":"","size":9539,"stargazers_count":8,"watchers_count":8,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":141,"forks":1,"open_issues":141,"watchers":8,"default_branch":"master"}},"base":{"label":"nengo:master","ref":"master","sha":"faada01598241819c07be56242ae9a5255532db4","user":{"login":"nengo","id":9197486,"avatar_url":"https://avatars.githubusercontent.com/u/9197486?v=3","gravatar_id":"","url":"https://api.github.com/users/nengo","html_url":"https://github.com/nengo","followers_url":"https://api.github.com/users/nengo/followers","following_url":"https://api.github.com/users/nengo/following{/other_user}","gists_url":"https://api.github.com/users/nengo/gists{/gist_id}","starred_url":"https://api.github.com/users/nengo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nengo/subscriptions","organizations_url":"https://api.github.com/users/nengo/orgs","repos_url":"https://api.github.com/users/nengo/repos","events_url":"https://api.github.com/users/nengo/events{/privacy}","received_events_url":"https://api.github.com/users/nengo/received_events","type":"Organization","site_admin":false},"repo":{"id":28949681,"name":"nengo_gui","full_name":"nengo/nengo_gui","owner":{"login":"nengo","id":9197486,"avatar_url":"https://avatars.githubusercontent.com/u/9197486?v=3","gravatar_id":"","url":"https://api.github.com/users/nengo","html_url":"https://github.com/nengo","followers_url":"https://api.github.com/users/nengo/followers","following_url":"https://api.github.com/users/nengo/following{/other_user}","gists_url":"https://api.github.com/users/nengo/gists{/gist_id}","starred_url":"https://api.github.com/users/nengo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nengo/subscriptions","organizations_url":"https://api.github.com/users/nengo/orgs","repos_url":"https://api.github.com/users/nengo/repos","events_url":"https://api.github.com/users/nengo/events{/privacy}","received_events_url":"https://api.github.com/users/nengo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nengo/nengo_gui","description":"Nengo interactive visualizer","fork":false,"url":"https://api.github.com/repos/nengo/nengo_gui","forks_url":"https://api.github.com/repos/nengo/nengo_gui/forks","keys_url":"https://api.github.com/repos/nengo/nengo_gui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nengo/nengo_gui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nengo/nengo_gui/teams","hooks_url":"https://api.github.com/repos/nengo/nengo_gui/hooks","issue_events_url":"https://api.github.com/repos/nengo/nengo_gui/issues/events{/number}","events_url":"https://api.github.com/repos/nengo/nengo_gui/events","assignees_url":"https://api.github.com/repos/nengo/nengo_gui/assignees{/user}","branches_url":"https://api.github.com/repos/nengo/nengo_gui/branches{/branch}","tags_url":"https://api.github.com/repos/nengo/nengo_gui/tags","blobs_url":"https://api.github.com/repos/nengo/nengo_gui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nengo/nengo_gui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nengo/nengo_gui/git/refs{/sha}","trees_url":"https://api.github.com/repos/nengo/nengo_gui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nengo/nengo_gui/statuses/{sha}","languages_url":"https://api.github.com/repos/nengo/nengo_gui/languages","stargazers_url":"https://api.github.com/repos/nengo/nengo_gui/stargazers","contributors_url":"https://api.github.com/repos/nengo/nengo_gui/contributors","subscribers_url":"https://api.github.com/repos/nengo/nengo_gui/subscribers","subscription_url":"https://api.github.com/repos/nengo/nengo_gui/subscription","commits_url":"https://api.github.com/repos/nengo/nengo_gui/commits{/sha}","git_commits_url":"https://api.github.com/repos/nengo/nengo_gui/git/commits{/sha}","comments_url":"https://api.github.com/repos/nengo/nengo_gui/comments{/number}","issue_comment_url":"https://api.github.com/repos/nengo/nengo_gui/issues/comments{/number}","contents_url":"https://api.github.com/repos/nengo/nengo_gui/contents/{+path}","compare_url":"https://api.github.com/repos/nengo/nengo_gui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nengo/nengo_gui/merges","archive_url":"https://api.github.com/repos/nengo/nengo_gui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nengo/nengo_gui/downloads","issues_url":"https://api.github.com/repos/nengo/nengo_gui/issues{/number}","pulls_url":"https://api.github.com/repos/nengo/nengo_gui/pulls{/number}","milestones_url":"https://api.github.com/repos/nengo/nengo_gui/milestones{/number}","notifications_url":"https://api.github.com/repos/nengo/nengo_gui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nengo/nengo_gui/labels{/name}","releases_url":"https://api.github.com/repos/nengo/nengo_gui/releases{/id}","created_at":"2015-01-08T05:00:06Z","updated_at":"2015-06-09T16:18:58Z","pushed_at":"2015-07-06T19:15:56Z","git_url":"git://github.com/nengo/nengo_gui.git","ssh_url":"git@github.com:nengo/nengo_gui.git","clone_url":"https://github.com/nengo/nengo_gui.git","svn_url":"https://github.com/nengo/nengo_gui","homepage":"","size":9539,"stargazers_count":8,"watchers_count":8,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":141,"forks":1,"open_issues":141,"watchers":8,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/460"},"html":{"href":"https://github.com/nengo/nengo_gui/pull/460"},"issue":{"href":"https://api.github.com/repos/nengo/nengo_gui/issues/460"},"comments":{"href":"https://api.github.com/repos/nengo/nengo_gui/issues/460/comments"},"review_comments":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/460/comments"},"review_comment":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nengo/nengo_gui/pulls/460/commits"},"statuses":{"href":"https://api.github.com/repos/nengo/nengo_gui/statuses/3fdc2b12214c637437ef72da4e6a09edf1c4f6bd"}}}}