Show output from print() calls in S&R functions

This commit is contained in:
Kovid Goyal 2014-11-19 17:53:19 +05:30
parent 48a701012c
commit b18d693ba2
3 changed files with 49 additions and 8 deletions

View File

@ -6,11 +6,12 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import re, io, weakref import re, io, weakref, sys
from cStringIO import StringIO
from PyQt5.Qt import ( from PyQt5.Qt import (
pyqtSignal, QVBoxLayout, QHBoxLayout, QPlainTextEdit, QLabel, QFontMetrics, pyqtSignal, QVBoxLayout, QHBoxLayout, QPlainTextEdit, QLabel, QFontMetrics,
QSize) QSize, Qt)
from calibre.ebooks.oeb.polish.utils import apply_func_to_match_groups from calibre.ebooks.oeb.polish.utils import apply_func_to_match_groups
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
@ -24,7 +25,7 @@ from calibre.utils.titlecase import titlecase
user_functions = JSONConfig('editor-search-replace-functions') user_functions = JSONConfig('editor-search-replace-functions')
def compile_code(src): def compile_code(src, name='<string>'):
if not isinstance(src, unicode): if not isinstance(src, unicode):
match = re.search(r'coding[:=]\s*([-\w.]+)', src[:200]) match = re.search(r'coding[:=]\s*([-\w.]+)', src[:200])
enc = match.group(1) if match else 'utf-8' enc = match.group(1) if match else 'utf-8'
@ -35,9 +36,10 @@ def compile_code(src):
src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE) src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE)
# Translate newlines to \n # Translate newlines to \n
src = io.StringIO(src, newline=None).getvalue() src = io.StringIO(src, newline=None).getvalue()
code = compile(src, name, 'exec')
namespace = {} namespace = {}
exec src in namespace exec code in namespace
return namespace return namespace
class Function(object): class Function(object):
@ -47,7 +49,7 @@ class Function(object):
self.is_builtin = source is None self.is_builtin = source is None
self.name = name self.name = name
if func is None: if func is None:
self.mod = compile_code(source) self.mod = compile_code(source, name)
self.func = self.mod['replace'] self.func = self.mod['replace']
else: else:
self.func = func self.func = func
@ -61,6 +63,7 @@ class Function(object):
self.match_index = 0 self.match_index = 0
self.boss = get_boss() self.boss = get_boss()
self.data = {} self.data = {}
self.debug_buf = StringIO()
def __hash__(self): def __hash__(self):
return hash(self.name) return hash(self.name)
@ -73,7 +76,11 @@ class Function(object):
def __call__(self, match): def __call__(self, match):
self.match_index += 1 self.match_index += 1
return self.func(match, self.match_index, self.context_name, self.boss.current_metadata, dictionaries, self.data, functions()) oo, oe, sys.stdout, sys.stderr = sys.stdout, sys.stderr, self.debug_buf, self.debug_buf
try:
return self.func(match, self.match_index, self.context_name, self.boss.current_metadata, dictionaries, self.data, functions())
finally:
sys.stdout, sys.stderr = oo, oe
@property @property
def source(self): def source(self):
@ -82,6 +89,29 @@ class Function(object):
return json.loads(P('editor-functions.json', data=True, allow_user_override=False))[self.name] return json.loads(P('editor-functions.json', data=True, allow_user_override=False))[self.name]
return self._source return self._source
class DebugOutput(Dialog):
def __init__(self, parent=None):
Dialog.__init__(self, 'Debug output', 'sr-function-debug-output')
self.setAttribute(Qt.WA_DeleteOnClose, False)
def setup_ui(self):
self.l = l = QVBoxLayout(self)
self.text = t = QPlainTextEdit(self)
l.addWidget(t)
l.addWidget(self.bb)
self.bb.setStandardButtons(self.bb.Close)
def show_log(self, name, text):
self.setWindowTitle(_('Debug output from %s') % name)
self.text.setPlainText(self.windowTitle() + '\n\n' + text)
self.show()
self.raise_()
def sizeHint(self):
fm = QFontMetrics(self.text.font())
return QSize(fm.averageCharWidth() * 120, 400)
def builtin_functions(): def builtin_functions():
for name, obj in globals().iteritems(): for name, obj in globals().iteritems():
if name.startswith('replace_') and callable(obj): if name.startswith('replace_') and callable(obj):
@ -183,7 +213,7 @@ class FunctionEditor(Dialog):
self.la2 = la = QLabel(_( self.la2 = la = QLabel(_(
'For help with creating functions, see the <a href="%s">User Manual</a>') % 'For help with creating functions, see the <a href="%s">User Manual</a>') %
'http://manual.calibre-ebook.com/edit.html#function-mode') 'http://manual.calibre-ebook.com/function_mode.html')
la.setOpenExternalLinks(True) la.setOpenExternalLinks(True)
l.addWidget(la) l.addWidget(la)
@ -207,7 +237,7 @@ class FunctionEditor(Dialog):
'You must specify a name for this function.'), show=True) 'You must specify a name for this function.'), show=True)
source = self.source source = self.source
try: try:
mod = compile_code(source) mod = compile_code(source, self.func_name)
except Exception as err: except Exception as err:
return error_dialog(self, _('Invalid python code'), _( return error_dialog(self, _('Invalid python code'), _(
'The code you created is not valid python code, with error: %s') % err, show=True) 'The code you created is not valid python code, with error: %s') % err, show=True)

View File

@ -1185,6 +1185,13 @@ def get_search_function(search):
raise NoSuchFunction(ans) raise NoSuchFunction(ans)
return ans return ans
def show_function_debug_output(func):
if isinstance(func, Function):
val = func.debug_buf.getvalue().strip()
if val:
from calibre.gui2.tweak_book.boss import get_boss
get_boss().gui.sr_debug_output.show_log(func.name, val)
def run_search( def run_search(
searches, action, current_editor, current_editor_name, searchable_names, searches, action, current_editor, current_editor_name, searchable_names,
gui_parent, show_editor, edit_file, show_current_diff, add_savepoint, rewind_savepoint, set_modified): gui_parent, show_editor, edit_file, show_current_diff, add_savepoint, rewind_savepoint, set_modified):
@ -1258,6 +1265,7 @@ def run_search(
if callable(repl): if callable(repl):
repl.init_env(current_editor_name) repl.init_env(current_editor_name)
if editor.replace(p, repl, saved_match='gui'): if editor.replace(p, repl, saved_match='gui'):
show_function_debug_output(repl)
return True return True
return no_replace(_( return no_replace(_(
'Currently selected text does not match the search query.')) 'Currently selected text does not match the search query.'))
@ -1302,6 +1310,7 @@ def run_search(
else: else:
num = len(p.findall(raw)) num = len(p.findall(raw))
count += num count += num
show_function_debug_output(repl)
for n in updates: for n in updates:
raw = raw_data[n] raw = raw_data[n]

View File

@ -39,6 +39,7 @@ from calibre.gui2.tweak_book.toc import TOCViewer
from calibre.gui2.tweak_book.char_select import CharSelect from calibre.gui2.tweak_book.char_select import CharSelect
from calibre.gui2.tweak_book.live_css import LiveCSS from calibre.gui2.tweak_book.live_css import LiveCSS
from calibre.gui2.tweak_book.manage_fonts import ManageFonts from calibre.gui2.tweak_book.manage_fonts import ManageFonts
from calibre.gui2.tweak_book.function_replace import DebugOutput
from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions
from calibre.gui2.tweak_book.editor.insert_resource import InsertImage from calibre.gui2.tweak_book.editor.insert_resource import InsertImage
from calibre.utils.icu import character_name, sort_key from calibre.utils.icu import character_name, sort_key
@ -238,6 +239,7 @@ class Main(MainWindow):
self.image_browser = InsertImage(self, for_browsing=True) self.image_browser = InsertImage(self, for_browsing=True)
self.insert_char = CharSelect(self) self.insert_char = CharSelect(self)
self.manage_fonts = ManageFonts(self) self.manage_fonts = ManageFonts(self)
self.sr_debug_output = DebugOutput(self)
self.create_actions() self.create_actions()
self.create_toolbars() self.create_toolbars()