auto import from //depot/cupcake/@135843
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
An interactive, stateful AJAX shell that runs Python code on the server.
|
||||
|
||||
Part of http://code.google.com/p/google-app-engine-samples/.
|
||||
|
||||
May be run as a standalone app or in an existing app as an admin-only handler.
|
||||
Can be used for system administration tasks, as an interactive way to try out
|
||||
APIs, or as a debugging aid during development.
|
||||
|
||||
The logging, os, sys, db, and users modules are imported automatically.
|
||||
|
||||
Interpreter state is stored in the datastore so that variables, function
|
||||
definitions, and other values in the global and local namespaces can be used
|
||||
across commands.
|
||||
|
||||
To use the shell in your app, copy shell.py, static/*, and templates/* into
|
||||
your app's source directory. Then, copy the URL handlers from app.yaml into
|
||||
your app.yaml.
|
||||
Binary file not shown.
@@ -1,308 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2007 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
An interactive, stateful AJAX shell that runs Python code on the server.
|
||||
|
||||
Part of http://code.google.com/p/google-app-engine-samples/.
|
||||
|
||||
May be run as a standalone app or in an existing app as an admin-only handler.
|
||||
Can be used for system administration tasks, as an interactive way to try out
|
||||
APIs, or as a debugging aid during development.
|
||||
|
||||
The logging, os, sys, db, and users modules are imported automatically.
|
||||
|
||||
Interpreter state is stored in the datastore so that variables, function
|
||||
definitions, and other values in the global and local namespaces can be used
|
||||
across commands.
|
||||
|
||||
To use the shell in your app, copy shell.py, static/*, and templates/* into
|
||||
your app's source directory. Then, copy the URL handlers from app.yaml into
|
||||
your app.yaml.
|
||||
|
||||
TODO: unit tests!
|
||||
"""
|
||||
|
||||
import logging
|
||||
import new
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
import wsgiref.handlers
|
||||
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp import template
|
||||
|
||||
|
||||
# Set to True if stack traces should be shown in the browser, etc.
|
||||
_DEBUG = True
|
||||
|
||||
# The entity kind for shell sessions. Feel free to rename to suit your app.
|
||||
_SESSION_KIND = '_Shell_Session'
|
||||
|
||||
# Types that can't be pickled.
|
||||
UNPICKLABLE_TYPES = (
|
||||
types.ModuleType,
|
||||
types.TypeType,
|
||||
types.ClassType,
|
||||
types.FunctionType,
|
||||
)
|
||||
|
||||
# Unpicklable statements to seed new sessions with.
|
||||
INITIAL_UNPICKLABLES = [
|
||||
'import logging',
|
||||
'import os',
|
||||
'import sys',
|
||||
'from google.appengine.ext import db',
|
||||
'from google.appengine.api import users',
|
||||
]
|
||||
|
||||
|
||||
class Session(db.Model):
|
||||
"""A shell session. Stores the session's globals.
|
||||
|
||||
Each session globals is stored in one of two places:
|
||||
|
||||
If the global is picklable, it's stored in the parallel globals and
|
||||
global_names list properties. (They're parallel lists to work around the
|
||||
unfortunate fact that the datastore can't store dictionaries natively.)
|
||||
|
||||
If the global is not picklable (e.g. modules, classes, and functions), or if
|
||||
it was created by the same statement that created an unpicklable global,
|
||||
it's not stored directly. Instead, the statement is stored in the
|
||||
unpicklables list property. On each request, before executing the current
|
||||
statement, the unpicklable statements are evaluated to recreate the
|
||||
unpicklable globals.
|
||||
|
||||
The unpicklable_names property stores all of the names of globals that were
|
||||
added by unpicklable statements. When we pickle and store the globals after
|
||||
executing a statement, we skip the ones in unpicklable_names.
|
||||
|
||||
Using Text instead of string is an optimization. We don't query on any of
|
||||
these properties, so they don't need to be indexed.
|
||||
"""
|
||||
global_names = db.ListProperty(db.Text)
|
||||
globals = db.ListProperty(db.Blob)
|
||||
unpicklable_names = db.ListProperty(db.Text)
|
||||
unpicklables = db.ListProperty(db.Text)
|
||||
|
||||
def set_global(self, name, value):
|
||||
"""Adds a global, or updates it if it already exists.
|
||||
|
||||
Also removes the global from the list of unpicklable names.
|
||||
|
||||
Args:
|
||||
name: the name of the global to remove
|
||||
value: any picklable value
|
||||
"""
|
||||
blob = db.Blob(pickle.dumps(value))
|
||||
|
||||
if name in self.global_names:
|
||||
index = self.global_names.index(name)
|
||||
self.globals[index] = blob
|
||||
else:
|
||||
self.global_names.append(db.Text(name))
|
||||
self.globals.append(blob)
|
||||
|
||||
self.remove_unpicklable_name(name)
|
||||
|
||||
def remove_global(self, name):
|
||||
"""Removes a global, if it exists.
|
||||
|
||||
Args:
|
||||
name: string, the name of the global to remove
|
||||
"""
|
||||
if name in self.global_names:
|
||||
index = self.global_names.index(name)
|
||||
del self.global_names[index]
|
||||
del self.globals[index]
|
||||
|
||||
def globals_dict(self):
|
||||
"""Returns a dictionary view of the globals.
|
||||
"""
|
||||
return dict((name, pickle.loads(val))
|
||||
for name, val in zip(self.global_names, self.globals))
|
||||
|
||||
def add_unpicklable(self, statement, names):
|
||||
"""Adds a statement and list of names to the unpicklables.
|
||||
|
||||
Also removes the names from the globals.
|
||||
|
||||
Args:
|
||||
statement: string, the statement that created new unpicklable global(s).
|
||||
names: list of strings; the names of the globals created by the statement.
|
||||
"""
|
||||
self.unpicklables.append(db.Text(statement))
|
||||
|
||||
for name in names:
|
||||
self.remove_global(name)
|
||||
if name not in self.unpicklable_names:
|
||||
self.unpicklable_names.append(db.Text(name))
|
||||
|
||||
def remove_unpicklable_name(self, name):
|
||||
"""Removes a name from the list of unpicklable names, if it exists.
|
||||
|
||||
Args:
|
||||
name: string, the name of the unpicklable global to remove
|
||||
"""
|
||||
if name in self.unpicklable_names:
|
||||
self.unpicklable_names.remove(name)
|
||||
|
||||
|
||||
class FrontPageHandler(webapp.RequestHandler):
|
||||
"""Creates a new session and renders the shell.html template.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
# set up the session. TODO: garbage collect old shell sessions
|
||||
session_key = self.request.get('session')
|
||||
if session_key:
|
||||
session = Session.get(session_key)
|
||||
else:
|
||||
# create a new session
|
||||
session = Session()
|
||||
session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
|
||||
session_key = session.put()
|
||||
|
||||
template_file = os.path.join(os.path.dirname(__file__), 'templates',
|
||||
'shell.html')
|
||||
session_url = '/?session=%s' % session_key
|
||||
vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
|
||||
'python_version': sys.version,
|
||||
'session': str(session_key),
|
||||
'user': users.get_current_user(),
|
||||
'login_url': users.create_login_url(session_url),
|
||||
'logout_url': users.create_logout_url(session_url),
|
||||
}
|
||||
rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
|
||||
self.response.out.write(rendered)
|
||||
|
||||
|
||||
class StatementHandler(webapp.RequestHandler):
|
||||
"""Evaluates a python statement in a given session and returns the result.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
self.response.headers['Content-Type'] = 'text/plain'
|
||||
|
||||
# extract the statement to be run
|
||||
statement = self.request.get('statement')
|
||||
if not statement:
|
||||
return
|
||||
|
||||
# the python compiler doesn't like network line endings
|
||||
statement = statement.replace('\r\n', '\n')
|
||||
|
||||
# add a couple newlines at the end of the statement. this makes
|
||||
# single-line expressions such as 'class Foo: pass' evaluate happily.
|
||||
statement += '\n\n'
|
||||
|
||||
# log and compile the statement up front
|
||||
try:
|
||||
logging.info('Compiling and evaluating:\n%s' % statement)
|
||||
compiled = compile(statement, '<string>', 'single')
|
||||
except:
|
||||
self.response.out.write(traceback.format_exc())
|
||||
return
|
||||
|
||||
# create a dedicated module to be used as this statement's __main__
|
||||
statement_module = new.module('__main__')
|
||||
|
||||
# use this request's __builtin__, since it changes on each request.
|
||||
# this is needed for import statements, among other things.
|
||||
import __builtin__
|
||||
statement_module.__builtins__ = __builtin__
|
||||
|
||||
# load the session from the datastore
|
||||
session = Session.get(self.request.get('session'))
|
||||
|
||||
# swap in our custom module for __main__. then unpickle the session
|
||||
# globals, run the statement, and re-pickle the session globals, all
|
||||
# inside it.
|
||||
old_main = sys.modules.get('__main__')
|
||||
try:
|
||||
sys.modules['__main__'] = statement_module
|
||||
statement_module.__name__ = '__main__'
|
||||
|
||||
# re-evaluate the unpicklables
|
||||
for code in session.unpicklables:
|
||||
exec code in statement_module.__dict__
|
||||
|
||||
# re-initialize the globals
|
||||
for name, val in session.globals_dict().items():
|
||||
try:
|
||||
statement_module.__dict__[name] = val
|
||||
except:
|
||||
msg = 'Dropping %s since it could not be unpickled.\n' % name
|
||||
self.response.out.write(msg)
|
||||
logging.warning(msg + traceback.format_exc())
|
||||
session.remove_global(name)
|
||||
|
||||
# run!
|
||||
old_globals = dict(statement_module.__dict__)
|
||||
try:
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
try:
|
||||
sys.stdout = self.response.out
|
||||
sys.stderr = self.response.out
|
||||
exec compiled in statement_module.__dict__
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
except:
|
||||
self.response.out.write(traceback.format_exc())
|
||||
return
|
||||
|
||||
# extract the new globals that this statement added
|
||||
new_globals = {}
|
||||
for name, val in statement_module.__dict__.items():
|
||||
if name not in old_globals or val != old_globals[name]:
|
||||
new_globals[name] = val
|
||||
|
||||
if True in [isinstance(val, UNPICKLABLE_TYPES)
|
||||
for val in new_globals.values()]:
|
||||
# this statement added an unpicklable global. store the statement and
|
||||
# the names of all of the globals it added in the unpicklables.
|
||||
session.add_unpicklable(statement, new_globals.keys())
|
||||
logging.debug('Storing this statement as an unpicklable.')
|
||||
|
||||
else:
|
||||
# this statement didn't add any unpicklables. pickle and store the
|
||||
# new globals back into the datastore.
|
||||
for name, val in new_globals.items():
|
||||
if not name.startswith('__'):
|
||||
session.set_global(name, val)
|
||||
|
||||
finally:
|
||||
sys.modules['__main__'] = old_main
|
||||
|
||||
session.put()
|
||||
|
||||
|
||||
def main():
|
||||
application = webapp.WSGIApplication(
|
||||
[('/gae_shell/', FrontPageHandler),
|
||||
('/gae_shell/shell.do', StatementHandler)], debug=_DEBUG)
|
||||
wsgiref.handlers.CGIHandler().run(application)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,308 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2007 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
An interactive, stateful AJAX shell that runs Python code on the server.
|
||||
|
||||
Part of http://code.google.com/p/google-app-engine-samples/.
|
||||
|
||||
May be run as a standalone app or in an existing app as an admin-only handler.
|
||||
Can be used for system administration tasks, as an interactive way to try out
|
||||
APIs, or as a debugging aid during development.
|
||||
|
||||
The logging, os, sys, db, and users modules are imported automatically.
|
||||
|
||||
Interpreter state is stored in the datastore so that variables, function
|
||||
definitions, and other values in the global and local namespaces can be used
|
||||
across commands.
|
||||
|
||||
To use the shell in your app, copy shell.py, static/*, and templates/* into
|
||||
your app's source directory. Then, copy the URL handlers from app.yaml into
|
||||
your app.yaml.
|
||||
|
||||
TODO: unit tests!
|
||||
"""
|
||||
|
||||
import logging
|
||||
import new
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
import wsgiref.handlers
|
||||
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp import template
|
||||
|
||||
|
||||
# Set to True if stack traces should be shown in the browser, etc.
|
||||
_DEBUG = True
|
||||
|
||||
# The entity kind for shell sessions. Feel free to rename to suit your app.
|
||||
_SESSION_KIND = '_Shell_Session'
|
||||
|
||||
# Types that can't be pickled.
|
||||
UNPICKLABLE_TYPES = (
|
||||
types.ModuleType,
|
||||
types.TypeType,
|
||||
types.ClassType,
|
||||
types.FunctionType,
|
||||
)
|
||||
|
||||
# Unpicklable statements to seed new sessions with.
|
||||
INITIAL_UNPICKLABLES = [
|
||||
'import logging',
|
||||
'import os',
|
||||
'import sys',
|
||||
'from google.appengine.ext import db',
|
||||
'from google.appengine.api import users',
|
||||
]
|
||||
|
||||
|
||||
class Session(db.Model):
|
||||
"""A shell session. Stores the session's globals.
|
||||
|
||||
Each session globals is stored in one of two places:
|
||||
|
||||
If the global is picklable, it's stored in the parallel globals and
|
||||
global_names list properties. (They're parallel lists to work around the
|
||||
unfortunate fact that the datastore can't store dictionaries natively.)
|
||||
|
||||
If the global is not picklable (e.g. modules, classes, and functions), or if
|
||||
it was created by the same statement that created an unpicklable global,
|
||||
it's not stored directly. Instead, the statement is stored in the
|
||||
unpicklables list property. On each request, before executing the current
|
||||
statement, the unpicklable statements are evaluated to recreate the
|
||||
unpicklable globals.
|
||||
|
||||
The unpicklable_names property stores all of the names of globals that were
|
||||
added by unpicklable statements. When we pickle and store the globals after
|
||||
executing a statement, we skip the ones in unpicklable_names.
|
||||
|
||||
Using Text instead of string is an optimization. We don't query on any of
|
||||
these properties, so they don't need to be indexed.
|
||||
"""
|
||||
global_names = db.ListProperty(db.Text)
|
||||
globals = db.ListProperty(db.Blob)
|
||||
unpicklable_names = db.ListProperty(db.Text)
|
||||
unpicklables = db.ListProperty(db.Text)
|
||||
|
||||
def set_global(self, name, value):
|
||||
"""Adds a global, or updates it if it already exists.
|
||||
|
||||
Also removes the global from the list of unpicklable names.
|
||||
|
||||
Args:
|
||||
name: the name of the global to remove
|
||||
value: any picklable value
|
||||
"""
|
||||
blob = db.Blob(pickle.dumps(value))
|
||||
|
||||
if name in self.global_names:
|
||||
index = self.global_names.index(name)
|
||||
self.globals[index] = blob
|
||||
else:
|
||||
self.global_names.append(db.Text(name))
|
||||
self.globals.append(blob)
|
||||
|
||||
self.remove_unpicklable_name(name)
|
||||
|
||||
def remove_global(self, name):
|
||||
"""Removes a global, if it exists.
|
||||
|
||||
Args:
|
||||
name: string, the name of the global to remove
|
||||
"""
|
||||
if name in self.global_names:
|
||||
index = self.global_names.index(name)
|
||||
del self.global_names[index]
|
||||
del self.globals[index]
|
||||
|
||||
def globals_dict(self):
|
||||
"""Returns a dictionary view of the globals.
|
||||
"""
|
||||
return dict((name, pickle.loads(val))
|
||||
for name, val in zip(self.global_names, self.globals))
|
||||
|
||||
def add_unpicklable(self, statement, names):
|
||||
"""Adds a statement and list of names to the unpicklables.
|
||||
|
||||
Also removes the names from the globals.
|
||||
|
||||
Args:
|
||||
statement: string, the statement that created new unpicklable global(s).
|
||||
names: list of strings; the names of the globals created by the statement.
|
||||
"""
|
||||
self.unpicklables.append(db.Text(statement))
|
||||
|
||||
for name in names:
|
||||
self.remove_global(name)
|
||||
if name not in self.unpicklable_names:
|
||||
self.unpicklable_names.append(db.Text(name))
|
||||
|
||||
def remove_unpicklable_name(self, name):
|
||||
"""Removes a name from the list of unpicklable names, if it exists.
|
||||
|
||||
Args:
|
||||
name: string, the name of the unpicklable global to remove
|
||||
"""
|
||||
if name in self.unpicklable_names:
|
||||
self.unpicklable_names.remove(name)
|
||||
|
||||
|
||||
class FrontPageHandler(webapp.RequestHandler):
|
||||
"""Creates a new session and renders the shell.html template.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
# set up the session. TODO: garbage collect old shell sessions
|
||||
session_key = self.request.get('session')
|
||||
if session_key:
|
||||
session = Session.get(session_key)
|
||||
else:
|
||||
# create a new session
|
||||
session = Session()
|
||||
session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
|
||||
session_key = session.put()
|
||||
|
||||
template_file = os.path.join(os.path.dirname(__file__), 'templates',
|
||||
'shell.html')
|
||||
session_url = '/?session=%s' % session_key
|
||||
vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
|
||||
'python_version': sys.version,
|
||||
'session': str(session_key),
|
||||
'user': users.get_current_user(),
|
||||
'login_url': users.create_login_url(session_url),
|
||||
'logout_url': users.create_logout_url(session_url),
|
||||
}
|
||||
rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
|
||||
self.response.out.write(rendered)
|
||||
|
||||
|
||||
class StatementHandler(webapp.RequestHandler):
|
||||
"""Evaluates a python statement in a given session and returns the result.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
self.response.headers['Content-Type'] = 'text/plain'
|
||||
|
||||
# extract the statement to be run
|
||||
statement = self.request.get('statement')
|
||||
if not statement:
|
||||
return
|
||||
|
||||
# the python compiler doesn't like network line endings
|
||||
statement = statement.replace('\r\n', '\n')
|
||||
|
||||
# add a couple newlines at the end of the statement. this makes
|
||||
# single-line expressions such as 'class Foo: pass' evaluate happily.
|
||||
statement += '\n\n'
|
||||
|
||||
# log and compile the statement up front
|
||||
try:
|
||||
logging.info('Compiling and evaluating:\n%s' % statement)
|
||||
compiled = compile(statement, '<string>', 'single')
|
||||
except:
|
||||
self.response.out.write(traceback.format_exc())
|
||||
return
|
||||
|
||||
# create a dedicated module to be used as this statement's __main__
|
||||
statement_module = new.module('__main__')
|
||||
|
||||
# use this request's __builtin__, since it changes on each request.
|
||||
# this is needed for import statements, among other things.
|
||||
import __builtin__
|
||||
statement_module.__builtins__ = __builtin__
|
||||
|
||||
# load the session from the datastore
|
||||
session = Session.get(self.request.get('session'))
|
||||
|
||||
# swap in our custom module for __main__. then unpickle the session
|
||||
# globals, run the statement, and re-pickle the session globals, all
|
||||
# inside it.
|
||||
old_main = sys.modules.get('__main__')
|
||||
try:
|
||||
sys.modules['__main__'] = statement_module
|
||||
statement_module.__name__ = '__main__'
|
||||
|
||||
# re-evaluate the unpicklables
|
||||
for code in session.unpicklables:
|
||||
exec code in statement_module.__dict__
|
||||
|
||||
# re-initialize the globals
|
||||
for name, val in session.globals_dict().items():
|
||||
try:
|
||||
statement_module.__dict__[name] = val
|
||||
except:
|
||||
msg = 'Dropping %s since it could not be unpickled.\n' % name
|
||||
self.response.out.write(msg)
|
||||
logging.warning(msg + traceback.format_exc())
|
||||
session.remove_global(name)
|
||||
|
||||
# run!
|
||||
old_globals = dict(statement_module.__dict__)
|
||||
try:
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
try:
|
||||
sys.stdout = self.response.out
|
||||
sys.stderr = self.response.out
|
||||
exec compiled in statement_module.__dict__
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
except:
|
||||
self.response.out.write(traceback.format_exc())
|
||||
return
|
||||
|
||||
# extract the new globals that this statement added
|
||||
new_globals = {}
|
||||
for name, val in statement_module.__dict__.items():
|
||||
if name not in old_globals or val != old_globals[name]:
|
||||
new_globals[name] = val
|
||||
|
||||
if True in [isinstance(val, UNPICKLABLE_TYPES)
|
||||
for val in new_globals.values()]:
|
||||
# this statement added an unpicklable global. store the statement and
|
||||
# the names of all of the globals it added in the unpicklables.
|
||||
session.add_unpicklable(statement, new_globals.keys())
|
||||
logging.debug('Storing this statement as an unpicklable.')
|
||||
|
||||
else:
|
||||
# this statement didn't add any unpicklables. pickle and store the
|
||||
# new globals back into the datastore.
|
||||
for name, val in new_globals.items():
|
||||
if not name.startswith('__'):
|
||||
session.set_global(name, val)
|
||||
|
||||
finally:
|
||||
sys.modules['__main__'] = old_main
|
||||
|
||||
session.put()
|
||||
|
||||
|
||||
def main():
|
||||
application = webapp.WSGIApplication(
|
||||
[('/', FrontPageHandler),
|
||||
('/shell.do', StatementHandler)], debug=_DEBUG)
|
||||
wsgiref.handlers.CGIHandler().run(application)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright 2007 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
* Javascript code for the interactive AJAX shell.
|
||||
*
|
||||
* Part of http://code.google.com/p/google-app-engine-samples/.
|
||||
*
|
||||
* Includes a function (shell.runStatement) that sends the current python
|
||||
* statement in the shell prompt text box to the server, and a callback
|
||||
* (shell.done) that displays the results when the XmlHttpRequest returns.
|
||||
*
|
||||
* Also includes cross-browser code (shell.getXmlHttpRequest) to get an
|
||||
* XmlHttpRequest.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shell namespace.
|
||||
* @type {Object}
|
||||
*/
|
||||
var shell = {}
|
||||
|
||||
/**
|
||||
* The shell history. history is an array of strings, ordered oldest to
|
||||
* newest. historyCursor is the current history element that the user is on.
|
||||
*
|
||||
* The last history element is the statement that the user is currently
|
||||
* typing. When a statement is run, it's frozen in the history, a new history
|
||||
* element is added to the end of the array for the new statement, and
|
||||
* historyCursor is updated to point to the new element.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
shell.history = [''];
|
||||
|
||||
/**
|
||||
* See {shell.history}
|
||||
* @type {number}
|
||||
*/
|
||||
shell.historyCursor = 0;
|
||||
|
||||
/**
|
||||
* A constant for the XmlHttpRequest 'done' state.
|
||||
* @type Number
|
||||
*/
|
||||
shell.DONE_STATE = 4;
|
||||
|
||||
/**
|
||||
* A cross-browser function to get an XmlHttpRequest object.
|
||||
*
|
||||
* @return {XmlHttpRequest?} a new XmlHttpRequest
|
||||
*/
|
||||
shell.getXmlHttpRequest = function() {
|
||||
if (window.XMLHttpRequest) {
|
||||
return new XMLHttpRequest();
|
||||
} else if (window.ActiveXObject) {
|
||||
try {
|
||||
return new ActiveXObject('Msxml2.XMLHTTP');
|
||||
} catch(e) {
|
||||
return new ActiveXObject('Microsoft.XMLHTTP');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the prompt textarea's onkeypress handler. Depending on the key that
|
||||
* was pressed, it will run the statement, navigate the history, or update the
|
||||
* current statement in the history.
|
||||
*
|
||||
* @param {Event} event the keypress event
|
||||
* @return {Boolean} false to tell the browser not to submit the form.
|
||||
*/
|
||||
shell.onPromptKeyPress = function(event) {
|
||||
var statement = document.getElementById('statement');
|
||||
|
||||
if (this.historyCursor == this.history.length - 1) {
|
||||
// we're on the current statement. update it in the history before doing
|
||||
// anything.
|
||||
this.history[this.historyCursor] = statement.value;
|
||||
}
|
||||
|
||||
// should we pull something from the history?
|
||||
if (event.ctrlKey && event.keyCode == 38 /* up arrow */) {
|
||||
if (this.historyCursor > 0) {
|
||||
statement.value = this.history[--this.historyCursor];
|
||||
}
|
||||
return false;
|
||||
} else if (event.ctrlKey && event.keyCode == 40 /* down arrow */) {
|
||||
if (this.historyCursor < this.history.length - 1) {
|
||||
statement.value = this.history[++this.historyCursor];
|
||||
}
|
||||
return false;
|
||||
} else if (!event.altKey) {
|
||||
// probably changing the statement. update it in the history.
|
||||
this.historyCursor = this.history.length - 1;
|
||||
this.history[this.historyCursor] = statement.value;
|
||||
}
|
||||
|
||||
// should we submit?
|
||||
var ctrlEnter = (document.getElementById('submit_key').value == 'ctrl-enter');
|
||||
if (event.keyCode == 13 /* enter */ && !event.altKey && !event.shiftKey &&
|
||||
event.ctrlKey == ctrlEnter) {
|
||||
return this.runStatement();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The XmlHttpRequest callback. If the request succeeds, it adds the command
|
||||
* and its resulting output to the shell history div.
|
||||
*
|
||||
* @param {XmlHttpRequest} req the XmlHttpRequest we used to send the current
|
||||
* statement to the server
|
||||
*/
|
||||
shell.done = function(req) {
|
||||
if (req.readyState == this.DONE_STATE) {
|
||||
var statement = document.getElementById('statement')
|
||||
statement.className = 'prompt';
|
||||
|
||||
// add the command to the shell output
|
||||
var output = document.getElementById('output');
|
||||
|
||||
output.value += '\n>>> ' + statement.value;
|
||||
statement.value = '';
|
||||
|
||||
// add a new history element
|
||||
this.history.push('');
|
||||
this.historyCursor = this.history.length - 1;
|
||||
|
||||
// add the command's result
|
||||
var result = req.responseText.replace(/^\s*|\s*$/g, ''); // trim whitespace
|
||||
if (result != '')
|
||||
output.value += '\n' + result;
|
||||
|
||||
// scroll to the bottom
|
||||
output.scrollTop = output.scrollHeight;
|
||||
if (output.createTextRange) {
|
||||
var range = output.createTextRange();
|
||||
range.collapse(false);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the form's onsubmit handler. It sends the python statement to the
|
||||
* server, and registers shell.done() as the callback to run when it returns.
|
||||
*
|
||||
* @return {Boolean} false to tell the browser not to submit the form.
|
||||
*/
|
||||
shell.runStatement = function() {
|
||||
var form = document.getElementById('form');
|
||||
|
||||
// build a XmlHttpRequest
|
||||
var req = this.getXmlHttpRequest();
|
||||
if (!req) {
|
||||
document.getElementById('ajax-status').innerHTML =
|
||||
"<span class='error'>Your browser doesn't support AJAX. :(</span>";
|
||||
return false;
|
||||
}
|
||||
|
||||
req.onreadystatechange = function() { shell.done(req); };
|
||||
|
||||
// build the query parameter string
|
||||
var params = '';
|
||||
for (i = 0; i < form.elements.length; i++) {
|
||||
var elem = form.elements[i];
|
||||
if (elem.type != 'submit' && elem.type != 'button' && elem.id != 'caret') {
|
||||
var value = escape(elem.value).replace(/\+/g, '%2B'); // escape ignores +
|
||||
params += '&' + elem.name + '=' + value;
|
||||
}
|
||||
}
|
||||
|
||||
// send the request and tell the user.
|
||||
document.getElementById('statement').className = 'prompt processing';
|
||||
req.open(form.method, form.action + '?' + params, true);
|
||||
req.setRequestHeader('Content-type',
|
||||
'application/x-www-form-urlencoded;charset=UTF-8');
|
||||
req.send(null);
|
||||
|
||||
return false;
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title> Interactive Shell </title>
|
||||
<script type="text/javascript" src="/gae_shell/static/shell.js"></script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.prompt, #output {
|
||||
width: 45em;
|
||||
border: 1px solid silver;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 10pt;
|
||||
margin: 0.5em;
|
||||
padding: 0.5em;
|
||||
padding-right: 0em;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
margin-left: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
#caret {
|
||||
width: 2.5em;
|
||||
margin-right: 0px;
|
||||
padding-right: 0px;
|
||||
border-right: 0px;
|
||||
}
|
||||
|
||||
#statement {
|
||||
width: 43em;
|
||||
margin-left: -1em;
|
||||
padding-left: 0px;
|
||||
border-left: 0px;
|
||||
background-position: top right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.processing {
|
||||
background-image: url("/gae_shell/static/spinner.gif");
|
||||
}
|
||||
|
||||
#ajax-status {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: #8AD;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #F44;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p> Interactive server-side Python shell for
|
||||
<a href="http://code.google.com/appengine/">Google App Engine</a>.
|
||||
(<a href="http://code.google.com/p/google-app-engine-samples/">source</a>)
|
||||
</p>
|
||||
|
||||
<textarea id="output" rows="22" readonly="readonly">
|
||||
{{ server_software }}
|
||||
Python {{ python_version }}
|
||||
</textarea>
|
||||
|
||||
<form id="form" action="shell.do" method="get">
|
||||
<nobr>
|
||||
<textarea class="prompt" id="caret" readonly="readonly" rows="4"
|
||||
onfocus="document.getElementById('statement').focus()"
|
||||
>>>></textarea>
|
||||
<textarea class="prompt" name="statement" id="statement" rows="4"
|
||||
onkeypress="return shell.onPromptKeyPress(event);"></textarea>
|
||||
</nobr>
|
||||
<input type="hidden" name="session" value="{{ session }}" />
|
||||
<input type="submit" style="display: none" />
|
||||
</form>
|
||||
|
||||
<p id="ajax-status"></p>
|
||||
|
||||
<p id="toolbar">
|
||||
{% if user %}
|
||||
<span class="username">{{ user.nickname }}</span>
|
||||
(<a href="{{ logout_url }}">log out</a>)
|
||||
{% else %}
|
||||
<a href="{{ login_url }}">log in</a>
|
||||
{% endif %}
|
||||
| Ctrl-Up/Down for history |
|
||||
<select id="submit_key">
|
||||
<option value="enter">Enter</option>
|
||||
<option value="ctrl-enter" selected="selected">Ctrl-Enter</option>
|
||||
</select>
|
||||
<label for="submit_key">submits</label>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('statement').focus();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user