Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: willmoffat/SaltySnake
base: 79854dc777
...
head fork: willmoffat/SaltySnake
compare: b1027d3e79
  • 5 commits
  • 16 files changed
  • 0 commit comments
  • 1 contributor
View
11 README.markdown
@@ -1,11 +1,12 @@
# Python in Chrome
-This demos shows how you can run Python as a Native Client module inside Google Chrome.
+This is a step-by-step guide to building a Chrome Web App using [Native Client](https://developers.google.com/native-client/) (NaCl) to compile the Python interpreter.
-<img src="http://willmoffat.github.com/SaltySnake/pages/images/screenshot.png" alt="screenshot" />
+*To install the example app please visit the [project homepage](http://willmoffat.github.com/SaltySnake/).*
-## Installing the Python Editor as a Chrome Web App
+## Building a Chrome Python app
-See `apps/editor`
+* Compile Python using the NaCl toolchain. _Optional_. See `py_patch/`.
+* Write a C++ NaCl module that provides an interface to the embedded Python. _Optional_. See `jspy/'.
+* Write a JavaScript Chrome App or Extension using the NaCl module. See `apps/editor`.
-TODO: link to sample apps on Chrome Web Store.
View
4 apps/editor/html/editor.html
@@ -11,10 +11,6 @@
<textarea id="output" readonly></textarea>
<div id="tip">Press Ctrl-Enter to execute the python code.</div>
- <embed id="jspy" width="0" height="0" src="/jspy/jspy.nmf" type="application/x-nacl" />
-
-
-
<script src="ace/ace-uncompressed.js"></script>
<script src="ace/theme-eclipse-uncompressed.js"></script>
<script src="ace/mode-python-uncompressed.js"></script>
View
6 apps/editor/html/editor.js
@@ -25,7 +25,11 @@ var jspy = (function() {
function init() {
if (DEBUG) console.log('JSPY.init');
- module = document.getElementById('jspy');
+ module = document.createElement('embed');
+ module.src = '/jspy/jspy.nmf';
+ module.type = 'application/x-nacl';
+ document.body.appendChild(module);
+
module.addEventListener('load', function() {
if (DEBUG) console.log('JSPY.load');
// TODO: grey out Run until module is ready.
View
1  apps/udacity_ext/Makefile
@@ -0,0 +1 @@
+include ../common_makefile
View
24 apps/udacity_ext/README.markdown
@@ -0,0 +1,24 @@
+TODO
+
+* support timeout
+* syntax checking
+
+* try autorun
+
+* package as ext and put on chrome web store
+* add py edit (to post on hacker news?)
+* screencast
+
+Maybe
+
+* Putscroll bars around editor. (rather than right of page and bottom of code).
+* BUG: grab new codemirror if editor changes
+* ?run on contract if dirty? (to save)
+* date + time to run
+
+DONE
+* shift-enter
+* remove autorun
+* empty response -> no output
+* line numbers for error - reset before run
+* better layout
View
54 apps/udacity_ext/background.js
@@ -0,0 +1,54 @@
+//HACK: some way to reload module?
+
+var jspy;
+
+var DEBUG = false;
+
+var waiting_callback;
+
+function initModule(callback) {
+ if (DEBUG) console.log('JsPy init');
+ if (jspy) {
+ jspy.parentNode.removeChild(jspy);
+ }
+ jspy = document.createElement('embed');
+ jspy.src = '/jspy/jspy.nmf';
+ jspy.type = 'application/x-nacl';
+ document.body.appendChild(jspy);
+ waiting_callback = function() { console.error('Should never be called'); };
+ jspy.addEventListener('load', function() { initModuleMessageHandler(callback); });
+}
+
+function initModuleMessageHandler(initCallback) {
+ if (DEBUG) console.log('JsPy Module loaded');
+ waiting_callback = null;
+ jspy.addEventListener('message', messageHandler);
+ initCallback();
+}
+
+function messageHandler(e) {
+ if (DEBUG) console.log('From JsPy:', e.data);
+ if (!waiting_callback) {
+ console.error('No waiting callback');
+ } else {
+ waiting_callback(e.data);
+ waiting_callback = null;
+ }
+}
+
+function handleContentScriptMessage(msg, sender, sendResponse) {
+ if (DEBUG) console.log('To JsPy:', msg);
+ if (msg === 'init') {
+ initModule(sendResponse);
+ return;
+ }
+ if (waiting_callback) {
+ sendResponse('stderr:JSPY error: outstanding callback');
+ return;
+ }
+ waiting_callback = sendResponse;
+ jspy.postMessage(msg);
+}
+
+
+chrome.extension.onRequest.addListener(handleContentScriptMessage);
View
BIN  apps/udacity_ext/images/arrow_contract.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  apps/udacity_ext/images/arrow_expand.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  apps/udacity_ext/images/icon_128.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  apps/udacity_ext/images/icon_16.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
17 apps/udacity_ext/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "Udacity CS101 Helper",
+ "description": "Expand the Udacity code editor to full screen and run Python super fast in Chrome.",
+ "version": "0.1",
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "content_scripts": [{
+ "matches": ["http://www.udacity.com/*"],
+ "run_at": "document_idle",
+ "js": ["ss_content_script.js"]
+ }],
+ "icons": {
+ "16": "images/icon_16.png",
+ "128": "images/icon_128.png"
+ }
+}
View
46 apps/udacity_ext/page.css
@@ -0,0 +1,46 @@
+/* Page - ContentScript communication element. */
+#ssDataNode { display:none; }
+
+/* In expanded mode only the editor is visible. */
+body.ssExpanded>* { display:none; }
+body.ssExpanded>.CodeMirror { display:block; }
+
+/* Better editor style for expanded mode. */
+body.ssExpanded .CodeMirror {
+ height: 100%;
+ border: none;
+}
+body.ssExpanded .CodeMirror-scroll {
+ height: auto;
+ overflow-y: hidden;
+ overflow-x: auto;
+ width: 100%;
+ border-right: solid 1px #bbb;
+}
+
+.ssErrorLine { color: red; font-weight:bold; }
+
+/* Show in expanded mode only. */
+.ssExpandedOnly { display: none; }
+body.ssExpanded .ssExpandedOnly { display:block; }
+
+/* Show in contracted mode only. */
+body.ssExpanded .ssContractedOnly { display: none; }
+
+#ssButtonExpand {position:absolute; top:0; right:16px; }
+#ssButtonContract {position:fixed; top:0; right:16px; }
+
+#ssColRight {
+ position: fixed;
+ top: 0; right: 0;
+ width: 42%; height: 100%;
+}
+
+.ssFloating { z-index:30000; cursor:pointer; opacity:0.8; background-color:white; }
+
+#ssButtonRun { cursor:pointer; position:fixed; top:11px; right:65px; }
+
+#ssOutput { width:100%; height:100%; overflow:auto; padding-top:3.3em; border:none; }
+
+.ssError { color:red; }
+
View
11 apps/udacity_ext/page.html
@@ -0,0 +1,11 @@
+<!-- TODO: credit http://findicons.com/pack/1688/web_blog for images -->
+<img id="ssButtonExpand" class="ssContractedOnly ssFloating" title="Enable Fullscreen (Escape)" src="BASEURL/images/arrow_expand.png"/>
+
+<div id="ssColRight" class="ssExpandedOnly">
+ <img id="ssButtonContract" class="ssFloating" title="Close Fullscreen (Escape)" src="BASEURL/images/arrow_contract.png"/>
+ <button id="ssButtonRun" class="orange-button" title="To execute your code press (Shift or Ctrl) + Enter">Run</button>
+ <textarea id="ssOutput" disabled>Run to see results</textarea>
+</div>
+
+<!-- Hidden element used for page/contentscript communication. -->
+<pre id="ssDataNode"/>
View
20 apps/udacity_ext/page.js
@@ -0,0 +1,20 @@
+var SS = {markers:[]};
+
+SS.getAssignment = function() { return App.current_nugget_controller.get("currentAssignment"); };
+SS.getEditor = function() { return SS.getAssignment().get("codeEditor"); };
+
+SS.focusEditor = function() { SS.getEditor().focus(); };
+
+SS.run = function() {
+ var cm = SS.getEditor();
+ while (SS.markers.length) { var m = SS.markers.pop(); cm.clearMarker(m); }
+ var val = cm.getValue();
+ document.getElementById("ssDataNode").textContent = val;
+};
+
+SS.markLine = function(lineNum) {
+ var cm = SS.getEditor();
+ cm.setCursor(lineNum, 10000);
+ SS.markers.push(cm.setMarker(lineNum, "", "ssErrorLine"));
+};
+
View
240 apps/udacity_ext/ss_content_script.js
@@ -0,0 +1,240 @@
+var DEBUG = false;
+
+var dom = {};
+
+var currentlyExpanded = false;
+
+var MSG_EMPTY_RESULT = 'Empty result. Did you forget to use "print"?';
+
+function setError(lineNum, msg) {
+ // Not using msg.
+ var script = 'SS.markLine(' + lineNum + ');';
+ injectScript(script);
+}
+
+function parseError(text) {
+ var lines = text.split('\n');
+ // Throw away last newline.
+ lines.pop();
+ var msg = lines.pop();
+ var match = null;
+ while (!match && lines.length) {
+ var r = /\bline (\d+)\b/.exec(lines.pop());
+ if (r) {
+ var line_num = parseInt(r[1]) - 1;
+ setError(line_num, msg);
+ return;
+ }
+ }
+ console.error('Could not parse', text);
+}
+
+function showOutput(result) {
+ var out;
+ var className = '';
+ if (result.stderr) {
+ className = 'ssError';
+ out = result.stderr;
+ parseError(result.stderr);
+ } else {
+ out = result.stdout || MSG_EMPTY_RESULT;
+ }
+ var el = document.getElementById('ssOutput');
+ el.textContent = out;
+ el.className = className;
+}
+
+function elide(str) {
+ if (str.length<80) return str;
+ return str.slice(0,80) + '...';
+}
+
+var PyRunner = (function() {
+ var state = {
+ running: false,
+ stopping: false
+ };
+
+ var decodeResponse = function(response) {
+ var i = response.indexOf(':');
+ var header = response.slice(0,i);
+ var data = response.slice(i+1);
+ var result = {};
+ result[header] = data;
+ return result;;
+ };
+
+ var send = function(cmd, data, callback) {
+ var msg = cmd;
+ if (data) {
+ msg += ':' + data;
+ }
+ if (DEBUG) console.log('JSPY.send:', elide(msg));
+ chrome.extension.sendRequest(msg, callback);
+ };
+
+ var init = function(callback) {
+ send('init', null, callback);
+ };
+
+ var run = function(pyCode, callback) {
+ if (state.running) {
+ stop(function() { run(pyCode, callback); });
+ }
+ state.running = true;
+ var wrapper = function(r) {
+ state.running = false;
+ callback(decodeResponse(r));
+ };
+ send('run', pyCode, wrapper);
+ };
+
+ var stop = function(callback) {
+ if (DEBUG) console.log('JSPY: stop');
+ if (state.stopping) {
+ console.error('Already stopping!');
+ return;
+ }
+ state.stopping = true;
+ var stop_cb = function(response) {
+ if (DEBUG) console.log('JSPY stopped', response);
+ // There is a bug in the JsPy module that causes the next 'run' command to fail.
+ // so send a dummy program. TODO: fix this!
+ // Once this has (failed to) execute, JsPy is back to normal.
+ var dummyCode = 'True';
+ var stop_cb2 = function() {
+ state.stopping = false;
+ callback();
+ };
+ send('run', dummyCode, stop_cb2);
+ };
+ send('stop', null, stop_cb);
+ };
+
+ return { init:init, run:run, stop:stop };
+})();
+
+
+function make(tagname, opt_parent, opt_props) {
+ var el = document.createElement(tagname);
+ for (k in opt_props) {
+ el[k] = opt_props[k];
+ }
+ if (opt_parent) {
+ opt_parent.appendChild(el);
+ }
+ return el;
+}
+
+
+function doExpandEditor() {
+ currentlyExpanded = true;
+ dom.editorOriginalParent = dom.editor.parentNode;
+ dom.editorNextSibling = dom.editor.nextSibling;
+ document.body.appendChild(dom.editor);
+
+ injectScript('SS.focusEditor()');
+
+ document.body.className += ' ssExpanded';
+
+ PyRunner.init(function() { console.log('SS JsPy loaded.'); });
+}
+
+function doContractEditor() {
+ currentlyExpanded = false;
+ dom.editorOriginalParent.insertBefore(dom.editor, dom.editorNextSibling);
+ document.body.className = document.body.className.replace(' ssExpanded', '');
+}
+
+function keyHandler(e) {
+ // Escape toggles
+ if (e.which === 27) {
+ currentlyExpanded ? doContractEditor() : doExpandEditor();
+ }
+
+ // No other keys are handled when in contracted mode.
+ if (!currentlyExpanded) return;
+
+ // Cmd/Ctrl-Enter runs.
+ if (e.which === 13 && (e.metaKey || e.ctrlKey || e.shiftKey)) {
+ doRun();
+ e.preventDefault();
+ }
+
+}
+
+function doRun() {
+ showOutput({stdout:'Running...'});
+ injectScript('SS.run();' );
+ setTimeout(sendToApp, 1);
+}
+
+function sendToApp() {
+ var pyCode = document.getElementById('ssDataNode').textContent;
+ PyRunner.run(pyCode, showOutput);
+}
+
+
+function loadFile(filename) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', chrome.extension.getURL(filename), false);
+ xhr.send(null);
+ if (xhr.status !== 200) {
+ console.error('Failed to load ' + filename, xhr);
+ }
+ return xhr.responseText;
+}
+
+function modifyEditor(editor) {
+ dom.editor = editor;
+
+ injectCss( loadFile('page.css' ));
+ injectHtml( loadFile('page.html'), editor);
+ injectScript(loadFile('page.js' ), true);
+
+ document.getElementById('ssButtonRun' ).addEventListener('click', doRun, false);
+ document.getElementById('ssButtonExpand' ).addEventListener('click', doExpandEditor, false);
+ document.getElementById('ssButtonContract').addEventListener('click', doContractEditor, false);
+ document.addEventListener('keydown', keyHandler, false);
+}
+
+function watchForEditor() {
+ var editor = document.querySelector('.CodeMirror');
+ if (editor && editor !== dom.editor) {
+ modifyEditor(editor);
+ }
+ if (DEBUG) console.log('.');
+ window.setTimeout(watchForEditor, 500);
+}
+
+function init() {
+ if (DEBUG) console.log('SS:init');
+ if (DEBUG) window.onerror = null; // Kill the Udacity error supressor.
+ watchForEditor();
+}
+
+init();
+
+function injectScript(code, keep) {
+ var script = make('script', document.head, {textContent:code});
+ if (!keep) {
+ setTimeout(function() { document.head.removeChild(script); }, 100);
+ }
+}
+
+function injectCss(css) {
+ make('style', document.head, {textContent:css});
+}
+
+function injectHtml(html, parent) {
+ var BASEURL = chrome.extension.getURL('');
+ html = html.replace(/BASEURL/g, BASEURL);
+
+ var dummy = make('div', null, {innerHTML:html});
+
+ var fragment = document.createDocumentFragment();
+ while(dummy.firstChild) {
+ fragment.appendChild(dummy.firstChild);
+ }
+ parent.appendChild(fragment);
+}
View
1  jspy/README.markdown
@@ -42,6 +42,7 @@ In a Chrome extensions page:
var data = 'print "Hello world!"';
jspy.postMessage(cmd + ':' + data);
</script>
+```
See `apps/` for real examples.

No commit comments for this range

Something went wrong with that request. Please try again.