This commit is contained in:
Massimo Di Pierro
2012-02-19 22:30:59 -06:00
parent cf9c827661
commit 4f1d3e79b4
12 changed files with 353 additions and 7 deletions
+1 -1
View File
@@ -1 +1 @@
Version 1.99.4 (2012-02-19 22:24:06) stable Version 1.99.4 (2012-02-19 22:30:50) stable
+161 -1
View File
@@ -1,8 +1,13 @@
import os
import sys import sys
import cStringIO import cStringIO
import gluon.contrib.shell import gluon.contrib.shell
import gluon.dal
import gluon.html
import gluon.validators
import code, thread import code, thread
from gluon.debug import communicate from gluon.debug import communicate, web_debugger, qdb_debugger
import pydoc
if DEMO_MODE or MULTI_USER_MODE: if DEMO_MODE or MULTI_USER_MODE:
@@ -32,4 +37,159 @@ def reset():
return 'done' return 'done'
# new implementation using qdb
def interact():
app = request.args(0) or 'admin'
reset()
# process all pending messages in the frontend
web_debugger.run()
# if debugging, filename and lineno should have valid values
filename = web_debugger.filename
lineno = web_debugger.lineno
if filename:
lines = dict([(i+1, l) for (i, l) in enumerate(
[l.strip("\n").strip("\r") for l
in open(filename).readlines()])])
filename = os.path.basename(filename)
else:
lines = {}
if filename:
env = web_debugger.do_environment()
f_locals = env['locals']
f_globals = {}
for name, value in env['globals'].items():
if name not in gluon.html.__all__ and \
name not in gluon.validators.__all__ and \
name not in gluon.dal.__all__:
f_globals[name] = pydoc.text.repr(value)
else:
f_locals = {}
f_globals = {}
response.headers['refresh'] = "3"
if web_debugger.exception_info:
response.flash = T('"User Exception" debug mode. '
'An error ticket could be issued!')
return dict(app=app, data="",
filename=web_debugger.filename, lines=lines, lineno=lineno,
f_globals=f_globals, f_locals=f_locals,
exception=web_debugger.exception_info)
def step():
web_debugger.do_step()
redirect(URL("interact"))
def next():
web_debugger.do_next()
redirect(URL("interact"))
def cont():
web_debugger.do_continue()
redirect(URL("interact"))
def ret():
web_debugger.do_return()
redirect(URL("interact"))
def stop():
web_debugger.do_quit()
redirect(URL("interact"))
def execute():
app = request.args[0]
command = request.vars.statement
session['debug_commands:'+app].append(command)
try:
output = web_debugger.do_exec(command)
if output is None:
output = ""
except Exception, e:
output = T("Exception %s") % str(e)
k = len(session['debug_commands:'+app]) - 1
return '[%i] %s%s\n' % (k + 1, command, output)
def breakpoints():
"Add or remove breakpoints"
# Get all .py files
files = listdir(apath('', r=request), '.*\.py$')
files = [filename for filename in files
if filename and 'languages' not in filename
and not filename.startswith("admin")
and not filename.startswith("examples")]
form = SQLFORM.factory(
Field('filename', requires=IS_IN_SET(files), label=T("Filename")),
Field('lineno', 'integer', label=T("Line number"),
requires=IS_NOT_EMPTY()),
Field('temporary', 'boolean', label=T("Temporary"),
comment=T("deleted after first hit")),
Field('condition', 'string', label=T("Condition"),
comment=T("honored only if the expression evaluates to true")),
)
if form.accepts(request.vars, session):
filename = os.path.join(request.env['applications_parent'],
'applications', form.vars.filename)
err = qdb_debugger.do_set_breakpoint(filename,
form.vars.lineno,
form.vars.temporary,
form.vars.condition)
response.flash = T("Set Breakpoint on %s at line %s: %s") % (
filename, form.vars.lineno, err or T('successful'))
for item in request.vars:
if item[:7] == 'delete_':
qdb_debugger.do_clear(item[7:])
breakpoints = [{'number': bp[0], 'filename': os.path.basename(bp[1]),
'path': bp[1], 'lineno': bp[2],
'temporary': bp[3], 'enabled': bp[4], 'hits': bp[5],
'condition': bp[6]}
for bp in qdb_debugger.do_list_breakpoint()]
return dict(breakpoints=breakpoints, form=form)
def toggle_breakpoint():
"Set or clear a breakpoint"
lineno = None
ok = None
try:
filename = os.path.join(request.env['applications_parent'],
'applications', request.vars.filename)
start = 0
sel_start = int(request.vars.sel_start)
for lineno, line in enumerate(request.vars.data.split("\n")):
if sel_start <= start:
break
start += len(line) + 1
else:
lineno = None
if lineno is not None:
for bp in qdb_debugger.do_list_breakpoint():
no, bp_filename, bp_lineno, temporary, enabled, hits, cond = bp
if filename == bp_filename and lineno == bp_lineno:
err = qdb_debugger.do_clear_breakpoint(filename, lineno)
response.flash = T("Removed Breakpoint on %s at line %s", (
filename, lineno))
ok = False
break
else:
err = qdb_debugger.do_set_breakpoint(filename, lineno)
response.flash = T("Set Breakpoint on %s at line %s: %s") % (
filename, lineno, err or T('successful'))
ok = True
else:
response.flash = T("Unable to determine the line number!")
except Exception, e:
session.flash = str(e)
return response.json({'ok': ok, 'lineno': lineno})
+3 -1
View File
@@ -120,7 +120,9 @@ if session.authorized:
else: else:
session.last_time = t0 session.last_time = t0
if not session.authorized and not \ if request.controller == "webservices":
pass
elif not session.authorized and not \
(request.controller == 'default' and \ (request.controller == 'default' and \
request.function in ('index','user')): request.function in ('index','user')):
+2
View File
@@ -26,6 +26,8 @@ if not session.authorized:
else: else:
response.menu.append((T('Logout'), False, response.menu.append((T('Logout'), False,
URL(_a,'default',f='logout'))) URL(_a,'default',f='logout')))
response.menu.append((T('Debug'), False,
URL(_a, 'debug','interact')))
if os.path.exists('applications/examples'): if os.path.exists('applications/examples'):
response.menu.append((T('Help'), False, URL('examples','default','index'))) response.menu.append((T('Help'), False, URL('examples','default','index')))
+13
View File
@@ -930,6 +930,19 @@ ul#snapshot > li {
background-image: url(../images/dim_bullet.gif); background-image: url(../images/dim_bullet.gif);
} }
/* Debug */
.debug h3.not_paused {
background-image: url(../images/red_bullet.gif);
}
.debug h3.exception {
margin-top: 0.5em;
background: url(../images/ticket_section.png) no-repeat;
padding: 5px 40px;
}
/* Footer */ /* Footer */
#footer { #footer {
@@ -36,6 +36,7 @@ function doClickSave() {
prepareDataForSave('data', data), prepareDataForSave('data', data),
prepareDataForSave('file_hash', jQuery("input[name='file_hash']").val()), prepareDataForSave('file_hash', jQuery("input[name='file_hash']").val()),
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()), prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
prepareDataForSave('from_ajax','true'))); prepareDataForSave('from_ajax','true')));
// console.info(area.textarea.value); // console.info(area.textarea.value);
jQuery("input[name='saved_on']").attr('style','background-color:yellow'); jQuery("input[name='saved_on']").attr('style','background-color:yellow');
@@ -94,6 +95,53 @@ function doClickSave() {
return false; return false;
} }
function doToggleBreakpoint(filename, url) {
try {
var data = eamy.instances[0].getText();
} catch(e) {
var data = area.textarea.value;
var sel = editAreaLoader.getSelectionRange('body');
}
var dataForPost = prepareMultiPartPOST(new Array(
prepareDataForSave('filename', filename),
prepareDataForSave('sel_start', sel["start"]),
prepareDataForSave('sel_end', sel["end"]),
prepareDataForSave('data', data)));
jQuery.ajax({
type: "POST",
contentType: 'multipart/form-data;boundary="' + dataForPost[1] + '"',
url: url,
dataType: "json",
data: dataForPost[0],
timeout: 5000,
beforeSend: function(xhr) {
xhr.setRequestHeader('web2py-component-location',document.location);
xhr.setRequestHeader('web2py-component-element','doSetBreakpoint');},
success: function(json,text,xhr){
// show flash message (if any)
var flash=xhr.getResponseHeader('web2py-component-flash');
if (flash) jQuery('.flash').html(flash).slideDown();
else jQuery('.flash').hide();
try {
if (json.error) {
window.location.href=json.redirect;
} else {
// mark the breakpoint if ok=True, remove mark if ok=False
// do nothing if ok = null
// alert(json.ok + json.lineno);
}
} catch(e) {
on_error();
}
},
error: function(json) { on_error(); }
});
return false;
}
function keepalive(url) { function keepalive(url) {
jQuery.ajax({ jQuery.ajax({
type: "GET", type: "GET",
@@ -36,6 +36,14 @@ jQuery(document).ready(function(){
{{pass}} {{pass}}
<p class="right controls"> <p class="right controls">
{{if filetype=='python':}}
{{=A(SPAN(T('toggle breakpoint')),
_value="breakpoint", _name="breakpoint",
_onclick="return doToggleBreakpoint('%s','%s://%s%s');" % (filename,
request.env['wsgi_url_scheme'], request.env['http_host'],
URL(c='debug', f='toggle_breakpoint')),
_class="button special")}}
{{pass}}
{{=button(URL('design',args=request.args[0]), T('back'))}} {{=button(URL('design',args=request.args[0]), T('back'))}}
{{if edit_controller:}} {{if edit_controller:}}
{{=button(edit_controller, T('edit controller'))}} {{=button(edit_controller, T('edit controller'))}}
+1 -1
View File
@@ -1970,7 +1970,7 @@ class PostgreSQLAdapter(BaseAdapter):
if self.drivers.get('psycopg2'): if self.drivers.get('psycopg2'):
self.driver = self.drivers['psycopg2'] self.driver = self.drivers['psycopg2']
elif self.drivers.get('pg8000'): elif self.drivers.get('pg8000'):
self.driver = drivers['pg8000'] self.driver = drivers.get('pg8000')
elif library == "postgres:psycopg2": elif library == "postgres:psycopg2":
self.driver = self.drivers.get('psycopg2') self.driver = self.drivers.get('psycopg2')
elif library == "postgres:pg8000": elif library == "postgres:pg8000":
+100
View File
@@ -10,6 +10,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
""" """
import logging import logging
import os
import pdb import pdb
import Queue import Queue
import sys import sys
@@ -82,7 +83,106 @@ def communicate(command=None):
return ''.join(result) return ''.join(result)
# New debugger implementation using qdb and a web UI
import gluon.contrib.qdb as qdb
from gluon import portalocker
def lock(name=''):
from gluon.globals import current
locker = open(os.path.join(current.request.folder, 'debug_%s.lock' % name),
'a')
portalocker.lock(locker, portalocker.LOCK_EX)
return locker
def unlock(locker):
portalocker.unlock(locker)
locker.close()
def check_interaction(fn):
"Decorator to clean and prevent interaction when not available"
def check_fn(self, *args, **kwargs):
locker = lock('interact')
try:
if self.filename:
self.clear_interaction()
fn(self, *args, **kwargs)
finally:
unlock(locker)
return check_fn
class WebDebugger(qdb.Frontend):
"Qdb web2py interface"
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None, skip=None):
qdb.Frontend.__init__(self, pipe)
self.clear_interaction()
def clear_interaction(self):
self.filename = None
self.lineno = None
self.exception_info = None
# redefine Frontend methods:
def run(self):
locker = lock('run')
try:
while self.pipe.poll():
qdb.Frontend.run(self)
finally:
unlock(locker)
def interaction(self, filename, lineno, line):
# store current status
locker = lock('interact')
try:
self.filename = filename
self.lineno = lineno
finally:
unlock(locker)
def exception(self, title, extype, exvalue, trace, request):
self.exception_info = {'title': title,
'extype': extype, 'exvalue': exvalue,
'trace': trace, 'request': request}
@check_interaction
def do_continue(self):
qdb.Frontend.do_continue(self)
@check_interaction
def do_step(self):
qdb.Frontend.do_step(self)
@check_interaction
def do_return(self):
qdb.Frontend.do_return(self)
@check_interaction
def do_next(self):
qdb.Frontend.do_next(self)
@check_interaction
def do_quit(self):
qdb.Frontend.do_quit(self)
# create the connection between threads:
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
front_conn = qdb.QueuePipe("parent", parent_queue, child_queue)
child_conn = qdb.QueuePipe("child", child_queue, parent_queue)
web_debugger = WebDebugger(front_conn) # frontend
qdb_debugger = qdb.Qdb(pipe=child_conn, redirect_stdio=False) # backend
dbg = qdb_debugger
import gluon.main
gluon.main.global_settings.debugging = True
+9
View File
@@ -255,6 +255,7 @@ def highlight(
counter=1, counter=1,
styles=None, styles=None,
highlight_line=None, highlight_line=None,
context_lines=None,
attributes=None, attributes=None,
): ):
styles = styles or {} styles = styles or {}
@@ -311,6 +312,14 @@ def highlight(
lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno]) lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno])
linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno]) linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno])
if context_lines:
if lineno + context_lines < len(lines):
del lines[lineno + context_lines:]
del linenumbers[lineno + context_lines:]
if lineno -context_lines > 0:
del lines[0:lineno - context_lines]
del linenumbers[0:lineno - context_lines]
code = '<br/>'.join(lines) code = '<br/>'.join(lines)
numbers = '<br/>'.join(linenumbers) numbers = '<br/>'.join(linenumbers)
+2
View File
@@ -1413,6 +1413,7 @@ class CODE(DIV):
link = self['link'] link = self['link']
counter = self.attributes.get('counter', 1) counter = self.attributes.get('counter', 1)
highlight_line = self.attributes.get('highlight_line', None) highlight_line = self.attributes.get('highlight_line', None)
context_lines = self.attributes.get('context_lines', None)
styles = self['styles'] or {} styles = self['styles'] or {}
return highlight( return highlight(
join(self.components), join(self.components),
@@ -1422,6 +1423,7 @@ class CODE(DIV):
styles=styles, styles=styles,
attributes=self.attributes, attributes=self.attributes,
highlight_line=highlight_line, highlight_line=highlight_line,
context_lines=context_lines,
) )
+5 -3
View File
@@ -490,6 +490,11 @@ def wsgibase(environ, responder):
# run controller # run controller
# ################################################## # ##################################################
if global_settings.debugging and request.application != "admin":
import gluon.debug
# activate the debugger and wait to reach application code
gluon.debug.dbg.do_debug(mainpyfile=request.folder)
serve_controller(request, response, session) serve_controller(request, response, session)
except HTTP, http_response: except HTTP, http_response:
@@ -591,9 +596,6 @@ def wsgibase(environ, responder):
if response and hasattr(response, 'session_file') \ if response and hasattr(response, 'session_file') \
and response.session_file: and response.session_file:
response.session_file.close() response.session_file.close()
# if global_settings.debugging:
# import gluon.debug
# gluon.debug.stop_trace()
session._unlock(response) session._unlock(response)
http_response, new_environ = rewrite.try_rewrite_on_error( http_response, new_environ = rewrite.try_rewrite_on_error(