Allow S&R funcs to specify they would like to be called once after all matches are found

This commit is contained in:
Kovid Goyal 2014-11-20 11:23:11 +05:30
parent cf42295fc5
commit 47b0baae85
3 changed files with 36 additions and 7 deletions

View File

@ -343,7 +343,15 @@ class TextEdit(PlainTextEdit):
if template is None: if template is None:
count = len(pat.findall(raw)) count = len(pat.findall(raw))
else: else:
from calibre.gui2.tweak_book.function_replace import Function
repl_is_func = isinstance(template, Function)
if repl_is_func:
template.init_env()
raw, count = pat.subn(template, raw) raw, count = pat.subn(template, raw)
if repl_is_func:
from calibre.gui2.tweak_book.search import show_function_debug_output
show_function_debug_output(template)
template.end()
if count > 0: if count > 0:
start_pos = min(c.anchor(), c.position()) start_pos = min(c.anchor(), c.position())
c.insertText(raw) c.insertText(raw)

View File

@ -11,7 +11,7 @@ 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, Qt) QSize, Qt, QApplication, QIcon)
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
@ -89,6 +89,14 @@ 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
def end(self):
if getattr(self.func, 'call_after_last_match', False):
oo, oe, sys.stdout, sys.stderr = sys.stdout, sys.stderr, self.debug_buf, self.debug_buf
try:
return self.func(None, self.match_index, self.context_name, self.boss.current_metadata, dictionaries, self.data, functions())
finally:
sys.stdout, sys.stderr = oo, oe
class DebugOutput(Dialog): class DebugOutput(Dialog):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -98,13 +106,18 @@ class DebugOutput(Dialog):
def setup_ui(self): def setup_ui(self):
self.l = l = QVBoxLayout(self) self.l = l = QVBoxLayout(self)
self.text = t = QPlainTextEdit(self) self.text = t = QPlainTextEdit(self)
self.log_text = ''
l.addWidget(t) l.addWidget(t)
l.addWidget(self.bb) l.addWidget(self.bb)
self.bb.setStandardButtons(self.bb.Close) self.bb.setStandardButtons(self.bb.Close)
self.cb = b = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole)
b.clicked.connect(self.copy_to_clipboard)
b.setIcon(QIcon(I('edit-copy.png')))
def show_log(self, name, text): def show_log(self, name, text):
self.setWindowTitle(_('Debug output from %s') % name) self.setWindowTitle(_('Debug output from %s') % name)
self.text.setPlainText(self.windowTitle() + '\n\n' + text) self.text.setPlainText(self.windowTitle() + '\n\n' + text)
self.log_text = text
self.show() self.show()
self.raise_() self.raise_()
@ -112,6 +125,9 @@ class DebugOutput(Dialog):
fm = QFontMetrics(self.text.font()) fm = QFontMetrics(self.text.font())
return QSize(fm.averageCharWidth() * 120, 400) return QSize(fm.averageCharWidth() * 120, 400)
def copy_to_clipboard(self):
QApplication.instance().clipboard().setText(self.log_text)
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):
@ -300,7 +316,6 @@ def replace_swapcase(match, number, file_name, metadata, dictionaries, data, fun
return apply_func_to_match_groups(match, swapcase) return apply_func_to_match_groups(match, swapcase)
if __name__ == '__main__': if __name__ == '__main__':
from PyQt5.Qt import QApplication
app = QApplication([]) app = QApplication([])
FunctionEditor().exec_() FunctionEditor().exec_()
del app del app

View File

@ -1262,9 +1262,12 @@ def run_search(
if editor is None: if editor is None:
return no_replace() return no_replace()
for p, repl in searches: for p, repl in searches:
if callable(repl): repl_is_func = isinstance(repl, Function)
if repl_is_func:
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'):
if repl_is_func:
repl.end()
show_function_debug_output(repl) show_function_debug_output(repl)
return True return True
return no_replace(_( return no_replace(_(
@ -1296,12 +1299,13 @@ def run_search(
raw_data[n] = raw raw_data[n] = raw
for p, repl in searches: for p, repl in searches:
if callable(repl): repl_is_func = isinstance(repl, Function)
if repl_is_func:
repl.init_env() repl.init_env()
for n, syntax in lfiles.iteritems(): for n, syntax in lfiles.iteritems():
raw = raw_data[n] raw = raw_data[n]
if replace: if replace:
if callable(repl): if repl_is_func:
repl.context_name = n repl.context_name = n
raw, num = p.subn(repl, raw) raw, num = p.subn(repl, raw)
if num > 0: if num > 0:
@ -1310,6 +1314,8 @@ def run_search(
else: else:
num = len(p.findall(raw)) num = len(p.findall(raw))
count += num count += num
if repl_is_func:
repl.end()
show_function_debug_output(repl) show_function_debug_output(repl)
for n in updates: for n in updates: