Skip to content

Commit

Permalink
Submitting Manuskript library + webconsole implementation.
Browse files Browse the repository at this point in the history
Adding webconsole-related dependencies to setup.py.
NOTE: bower_components folder contains third-party JS libraries.

BUG=
R=scudette@gmail.com

Review URL: https://codereview.appspot.com/87820043
  • Loading branch information
mbushkov committed Apr 15, 2014
1 parent 151bed2 commit 3202f82
Show file tree
Hide file tree
Showing 1,189 changed files with 130,535 additions and 0 deletions.
Empty file added manuskript/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions manuskript/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import StringIO


class Plugin(object):
ANGULAR_MODULE = None

JS_FILES = []
CSS_FILES = []

@classmethod
def PlugIntoApp(cls, app):
pass

@classmethod
def GenerateHTML(cls):
out = StringIO.StringIO()

for js_file in cls.JS_FILES:
out.write("""<script src="%s"></script>\n""" % js_file)

for css_file in cls.CSS_FILES:
out.write("""<link rel="stylesheet" href="%s"></link>\n""" %
css_file)

if cls.ANGULAR_MODULE:
out.write("""
<script>var manuskriptPluginsList = manuskriptPluginsList || [];\n
manuskriptPluginsList.push("%s");</script>\n""" % cls.ANGULAR_MODULE)

return out.getvalue()
3 changes: 3 additions & 0 deletions manuskript/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from manuskript.plugins.plaintext import PlainText
from manuskript.plugins.markdown import Markdown
from manuskript.plugins.pythoncall import PythonCall
8 changes: 8 additions & 0 deletions manuskript/plugins/markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from manuskript import plugin


class Markdown(plugin.Plugin):
ANGULAR_MODULE = "manuskript.markdown"

JS_FILES = ["/static/components/markdown/markdown-controller.js",
"/static/components/markdown/markdown.js"]
9 changes: 9 additions & 0 deletions manuskript/plugins/plaintext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from manuskript import plugin


class PlainText(plugin.Plugin):
ANGULAR_MODULE = "manuskript.plaintext"

JS_FILES = ["/static/components/plaintext/plaintext-controller.js",
"/static/components/plaintext/plaintext.js"]
CSS_FILES = ["/static/components/plaintext/plaintext.css"]
65 changes: 65 additions & 0 deletions manuskript/plugins/pythoncall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from flask import jsonify
from flask import request

from manuskript import plugin
from manuskript import pythonshell


class PythonCall(plugin.Plugin):
ANGULAR_MODULE = "manuskript.pythoncall"

JS_FILES = ["/static/components/pythoncall/renderer-service.js",
"/static/components/pythoncall/pythoncall-controller.js",
"/static/components/pythoncall/pythoncall.js"]

CSS_FILES = ["/static/components/pythoncall/pythoncall.css"]

@classmethod
def UpdatePythonShell(cls, app, shell):
pass

@classmethod
def PlugIntoApp(cls, app):

@app.route("/session/reset", methods=["POST"])
def session_reset(): # pylint: disable=unused-variable
app.config[cls.__name__] = shell = pythonshell.PythonShell()
cls.UpdatePythonShell(app, shell)

@app.route("/controllers/pythoncall", methods=["POST"])
def python_call(): # pylint: disable=unused-variable
if cls.__name__ not in app.config:
app.config[cls.__name__] = shell = pythonshell.PythonShell()
cls.UpdatePythonShell(app, shell)
shell = app.config[cls.__name__]

source_code = request.get_json()["source"]

result = None
error = None
is_parsing_error = False

try:
stdout, stderr, result = shell.Exec("\n".join(source_code))
except pythonshell.ParseError as e:
stdout, stderr, error = "", "", e.original_error
is_parsing_error = True
except pythonshell.ExecError as e:
stdout, stderr, error = e.stdout, e.stderr, e.original_error

stdout_lines = stdout and stdout.split("\n") or []
stderr_lines = stderr and stderr.split("\n") or []
if not error:
result_lines = result and str(result).split("\n") or []
error_lines = []
else:
result_lines = []
error_lines = str(error).split("\n")

response = jsonify(data=dict(stdout=stdout_lines,
stderr=stderr_lines,
result=result_lines,
error=error_lines,
is_parsing_error=is_parsing_error,
execution_count=shell.execution_count))
return response
87 changes: 87 additions & 0 deletions manuskript/pythonshell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import ast
import codegen
import StringIO
import sys


class Error(Exception):
"""PythonShell-specific error."""


class ParseError(Error):
"""Thrown when supplied code can't be parsed."""

def __init__(self, original_error):
super(ParseError, self).__init__()
self.original_error = original_error


class ExecError(Error):
"""Thrown when supplied code raises exception during execution."""

def __init__(self, stdout, stderr, original_error):
super(ExecError, self).__init__()
self.stdout = stdout
self.stderr = stderr
self.original_error = original_error


class PythonShell(object):
"""Implementation of the python shell."""

def __init__(self, global_context=None, local_context=None,
filename="<unknown>"):
super(PythonShell, self).__init__()
self.global_context = global_context or {}
self.local_context = local_context or {}
self.filename = filename
self.execution_count = 0

def Exec(self, source):
self.execution_count += 1

try:
nodes = ast.parse(source, self.filename)
except IndentationError as e:
raise ParseError(e)
except (OverflowError, SyntaxError, ValueError,
TypeError, MemoryError) as e:
raise ParseError(e)

stdout = StringIO.StringIO()
stderr = StringIO.StringIO()
prev_stdout = sys.stdout
prev_stderr = sys.stderr
sys.stdout = stdout
sys.stderr = stderr

try:
if isinstance(nodes.body[-1], ast.Expr):
exec_nodes = nodes.body[:-1]
interactive_nodes = nodes.body[-1:]
else:
exec_nodes, interactive_nodes = nodes.body, []

for node in exec_nodes:
mod = ast.Module([node])
code = compile(mod, self.filename, "exec")
exec(code, self.global_context, self.local_context)

result = None
for node in interactive_nodes:
source = codegen.to_source(node)
new_node = ast.parse(source, self.filename, mode="eval")
mod = ast.Expression(new_node.body)
code = compile(mod, self.filename, "eval")
result = eval(code, self.global_context, self.local_context)

sys.stdout = prev_stdout
sys.stderr = prev_stderr

return stdout.getvalue(), stderr.getvalue(), result
except Exception as e:
raise ExecError(stdout.getvalue(), stderr.getvalue(), e)

finally:
sys.stdout = prev_stdout
sys.stderr = prev_stderr
57 changes: 57 additions & 0 deletions manuskript/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import os
import StringIO

from flask import Flask
from flask import current_app
from flask import helpers

from manuskript import plugins as manuskript_plugins


STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")

DEFAULT_PLUGINS = [manuskript_plugins.PlainText,
manuskript_plugins.Markdown,
manuskript_plugins.PythonCall]


def RunServer(host="localhost", port=5000, debug=False, plugins=None,
config=None):
if not plugins:
plugins = DEFAULT_PLUGINS

if not config:
config = {}

app = Flask(__name__, static_folder=STATIC_PATH)
app.config["manuskript_plugins"] = plugins

# Configure index route
@app.route("/")
def index(): # pylint: disable=unused-variable
plugins_snippets = [p.GenerateHTML()
for p in current_app.config['manuskript_plugins']]

with open(os.path.join(STATIC_PATH, "index.html")) as fd:
contents = fd.read()
contents = contents.replace("<!-- manuskript-plugins -->",
"\n".join(plugins_snippets))

return helpers.send_file(StringIO.StringIO(contents),
mimetype="text/html",
conditional=True)

# Turn off caching for easier development/debugging
@app.after_request
def add_header(response): # pylint: disable=unused-variable
"""Turn off caching for easier debugging."""
response.headers['Cache-Control'] = 'no-cache, no-store'
return response

for plugin_cls in plugins:
plugin_cls.PlugIntoApp(app)

for k, v in config.items():
app.config[k] = v

app.run(host=host, port=port, debug=debug)
20 changes: 20 additions & 0 deletions manuskript/standalone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import argparse

from manuskript import server


PARSER = argparse.ArgumentParser()
PARSER.add_argument("--host", type=str,
help="Web server host address.")
PARSER.add_argument("--port", type=int,
help="Web server port.")
PARSER.add_argument("--debug", type=bool,
help="If true, reload files on change.")


def main():
args = PARSER.parse_args()
server.RunServer(host=args.host, port=args.port, debug=args.debug)

if __name__ == "__main__":
main()
47 changes: 47 additions & 0 deletions manuskript/static/.jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"node" : true,
"browser" : true,
"es5" : true,
"esnext" : true,
"bitwise" : true,
"camelcase": true,
"curly" : true,
"eqeqeq" : true,
"immed" : true,
"indent" : 2,
"latedef" : true,
"newcap" : true,
"noarg" : true,
"quotmark" : "single",
"regexp" : true,
"undef" : true,
"unused" : true,
"strict" : true,
"trailing" : false,
"smarttabs": true,
"white" : false,
"globals" : {
"$" : false,
"angular" : false,
"browser" : false,
"repeater" : false,
"element" : false,
"inject" : false,
"afterEach" : false,
"beforeEach" : false,
"confirm" : false,
"context" : false,
"describe" : false,
"expect" : false,
"it" : false,
"jasmine" : false,
"JSHINT" : false,
"mostRecentAjaxRequest": false,
"qq" : false,
"runs" : false,
"spyOn" : false,
"spyOnEvent" : false,
"waitsFor" : false,
"xdescribe" : false
}
}
18 changes: 18 additions & 0 deletions manuskript/static/Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var path = require('path');

module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),

jshint: {
files: ['*.js', 'components/**/*.js'],
options: {
jshintrc: true,
}
}
});

grunt.loadNpmTasks('grunt-contrib-jshint');

grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint']);
}
Loading

0 comments on commit 3202f82

Please sign in to comment.