This commit is contained in:
Kovid Goyal 2022-10-24 21:30:34 +05:30
commit 0871ba81aa
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 145 additions and 50 deletions

View File

@ -7,6 +7,7 @@ __license__ = 'GPL v3'
import json, os, traceback, re
from functools import partial
import sys
from qt.core import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont,
QApplication, QTextCharFormat, QColor, QCursor,
@ -389,6 +390,18 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
self.builtins = (builtin_functions if builtin_functions else
formatter_functions().get_builtins_and_aliases())
# Set up the breakpoint bar
s = gprefs.get('template_editor_break_on_print', False)
self.go_button.setEnabled(s)
self.remove_all_button.setEnabled(s)
self.set_all_button.setEnabled(s)
self.toggle_button.setEnabled(s)
self.breakpoint_line_box.setEnabled(s)
self.breakpoint_line_box_label.setEnabled(s)
self.break_box.setChecked(s)
self.break_box.stateChanged.connect(self.break_box_changed)
self.go_button.clicked.connect(self.go_button_pressed)
# Set up the display table
self.table_column_widths = None
try:
@ -451,16 +464,6 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
'<a href="{}">{}</a>'.format(
localize_user_manual_link('https://manual.calibre-ebook.com/generated/en/template_ref.html'), tt))
s = gprefs.get('template_editor_break_on_print', False)
self.go_button.setEnabled(s)
self.remove_all_button.setEnabled(s)
self.set_all_button.setEnabled(s)
self.toggle_button.setEnabled(s)
self.breakpoint_line_box.setEnabled(s)
self.breakpoint_line_box_label.setEnabled(s)
self.break_box.setChecked(s)
self.break_box.stateChanged.connect(self.break_box_changed)
self.go_button.clicked.connect(self.go_button_pressed)
self.textbox.setFocus()
self.set_up_font_boxes()
self.toggle_button.clicked.connect(self.toggle_button_pressed)
@ -546,11 +549,19 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
for r in range(0, len(mi)):
w = QLineEdit(tv)
w.setReadOnly(True)
w.setText(mi[r].title)
tv.setCellWidget(r, 0, w)
w = QLineEdit(tv)
w.setReadOnly(True)
tv.setCellWidget(r, 1, w)
self.display_values('')
self.set_waiting_message()
def set_waiting_message(self):
if self.break_box.isChecked():
for i in range(len(self.mi)):
self.template_value.cellWidget(i, 1).setText('')
self.template_value.cellWidget(0, 1).setText(
_("*** Breakpoints are enabled. Waiting for the 'Go' button to be pressed"))
def show_context_menu(self, point):
m = self.textbox.createStandardContextMenu()
@ -678,6 +689,8 @@ def evaluate(book, context):
self.breakpoint_line_box_label.setEnabled(new_state != 0)
if new_state == 0:
self.display_values(str(self.textbox.toPlainText()))
else:
self.set_waiting_message()
def go_button_pressed(self):
self.display_values(str(self.textbox.toPlainText()))
@ -760,14 +773,17 @@ def evaluate(book, context):
c = app.clipboard()
c.setText(str(self.icon_files.currentText()))
@property
def is_python(self):
return self.textbox.toPlainText().startswith('python:')
def textbox_changed(self):
cur_text = str(self.textbox.toPlainText())
if cur_text.startswith('python:'):
if self.is_python:
if self.highlighting_gpm is True:
self.highlighter.initialize_rules(self.builtins, True)
self.highlighting_gpm = False
self.break_box.setChecked(False)
self.break_box.setEnabled(False)
self.break_box.setEnabled(True)
elif not self.highlighting_gpm:
self.highlighter.initialize_rules(self.builtins, False)
self.highlighting_gpm = True
@ -776,8 +792,33 @@ def evaluate(book, context):
self.last_text = cur_text
self.highlighter.regenerate_paren_positions()
self.text_cursor_changed()
if self.break_box.checkState() == Qt.CheckState.Unchecked:
if not self.break_box.isChecked():
self.display_values(cur_text)
else:
self.set_waiting_message()
def trace_lines(self, frame, event, arg):
if event != 'line':
return
# Only respond to events in the "string" which is the template
if frame.f_code.co_filename != '<string>':
return
# Check that there is a breakpoint at the line
if frame.f_lineno not in self.textbox.clicked_line_numbers:
return
l = self.template_value.selectionModel().selectedRows()
mi_to_use = self.mi[0 if len(l) == 0 else l[0].row()]
self.break_reporter_dialog = PythonBreakReporter(self, mi_to_use, frame)
if not self.break_reporter_dialog.exec():
raise StopException()
def trace_calls(self, frame, event, arg):
if event != 'call':
return
# If this is the "string" file (the template), return the trace_lines function
if frame.f_code.co_filename == '<string>':
return self.trace_lines
return None
def display_values(self, txt):
tv = self.template_value
@ -787,14 +828,21 @@ def evaluate(book, context):
w = tv.cellWidget(r, 0)
w.setText(mi.title)
w.setCursorPosition(0)
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION:'),
mi, global_vars=self.global_vars,
template_functions=self.all_functions,
break_reporter=self.break_reporter if r == break_on_mi else None,
python_context_object=self.python_context_object)
w = tv.cellWidget(r, 1)
w.setText(v.translate(translate_table))
w.setCursorPosition(0)
if self.break_box.isChecked() and r == break_on_mi and self.is_python:
sys.settrace(self.trace_calls)
else:
sys.settrace(None)
try:
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION:'),
mi, global_vars=self.global_vars,
template_functions=self.all_functions,
break_reporter=self.break_reporter if r == break_on_mi else None,
python_context_object=self.python_context_object)
w = tv.cellWidget(r, 1)
w.setText(v.translate(translate_table))
w.setCursorPosition(0)
finally:
sys.settrace(None)
def text_cursor_changed(self):
cursor = self.textbox.textCursor()
@ -896,20 +944,21 @@ class BreakReporterItem(QTableWidgetItem):
def __init__(self, txt):
super().__init__(txt.translate(translate_table) if txt else txt)
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable|Qt.ItemFlag.ItemIsSelectable))
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable))
class BreakReporter(QDialog):
class BreakReporterBase(QDialog):
def __init__(self, parent, mi, op_label, op_value, locals_, line_number):
super().__init__(parent)
def setup_ui(self, mi, line_number, locals_, leading_rows):
self.mi = mi
self.leading_rows = leading_rows
self.setModal(True)
l = QVBoxLayout(self)
t = self.table = QTableWidget(self)
t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
t.setColumnCount(2)
t.setHorizontalHeaderLabels((_('Name'), _('Value')))
t.setRowCount(2)
t.setRowCount(leading_rows)
l.addWidget(t)
self.table_column_widths = None
@ -935,35 +984,34 @@ class BreakReporter(QDialog):
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.setLayout(l)
self.setWindowTitle(_('Break: line {0}, book {1}').format(line_number, self.mi.title))
local_names = sorted(locals_.keys())
rows = len(local_names)
self.table.setRowCount(rows+2)
self.table.setItem(0, 0, BreakReporterItem(op_label))
self.table.item(0,0).setToolTip(_('The name of the template language operation'))
self.table.setItem(0, 1, BreakReporterItem(op_value))
self.mi_combo = QComboBox()
t.setCellWidget(1, 0, self.mi_combo)
t.setCellWidget(leading_rows-1, 0, self.mi_combo)
self.mi_combo.addItems(self.get_field_keys())
self.mi_combo.setToolTip('Choose a book metadata field to display')
self.mi_combo.setCurrentIndex(-1)
self.mi_combo.currentTextChanged.connect(self.get_field_value)
for i,k in enumerate(local_names):
itm = BreakReporterItem(k)
itm.setToolTip(_('A variable in the template'))
self.table.setItem(i+2, 0, itm)
itm = BreakReporterItem(locals_[k])
itm.setToolTip(_('The value of the variable'))
self.table.setItem(i+2, 1, itm)
self.mi_combo.setCurrentIndex(self.mi_combo.findText('title'))
self.restore_geometry(gprefs, 'template_editor_break_geometry')
self.setup_locals(locals_)
def setup_locals(self, locals_):
raise NotImplementedError
def add_local_line(self, locals, row, key):
itm = BreakReporterItem(key)
itm.setToolTip(_('A variable in the template'))
self.table.setItem(row, 0, itm)
itm = BreakReporterItem(repr(locals[key]))
itm.setToolTip(_('The value of the variable'))
self.table.setItem(row, 1, itm)
def get_field_value(self, field):
val = self.mi.format_field('timestamp' if field == 'date' else field)[1]
self.table.setItem(1, 1, BreakReporterItem(val))
val = self.displayable_field_value(self.mi, field)
self.table.setItem(self.leading_rows-1, 1, BreakReporterItem(val))
def displayable_field_value(self, mi, field):
raise NotImplementedError
def table_column_resized(self, col, old, new):
self.table_column_widths = []
@ -992,6 +1040,49 @@ class BreakReporter(QDialog):
QDialog.accept(self)
class BreakReporter(BreakReporterBase):
def __init__(self, parent, mi, op_label, op_value, locals_, line_number):
super().__init__(parent)
self.setup_ui(mi, line_number, locals_, leading_rows=2)
self.table.setItem(0, 0, BreakReporterItem(op_label))
self.table.item(0,0).setToolTip(_('The name of the template language operation'))
self.table.setItem(0, 1, BreakReporterItem(op_value))
def setup_locals(self, locals):
local_names = sorted(locals.keys())
rows = len(local_names)
self.table.setRowCount(rows+2)
for i,k in enumerate(local_names, start=2):
self.add_local_line(locals, i, k)
def displayable_field_value(self, mi, field):
return self.mi.format_field('timestamp' if field == 'date' else field)[1]
class PythonBreakReporter(BreakReporterBase):
def __init__(self, parent, mi, frame):
super().__init__(parent)
self.frame = frame
line_number = frame.f_lineno
locals = frame.f_locals
self.setup_ui(mi, line_number, locals, leading_rows=1)
def setup_locals(self, locals):
locals = self.frame.f_locals
local_names = sorted(k for k in locals.keys() if k not in ('book', 'context'))
rows = len(local_names)
self.table.setRowCount(rows+1)
for i,k in enumerate(local_names, start=1):
if k in ('book', 'context'): continue
self.add_local_line(locals, i, k)
def displayable_field_value(self, mi, field):
return repr(self.mi.get('timestamp' if field == 'date' else field))
class EmbeddedTemplateDialog(TemplateDialog):
def __init__(self, parent):

View File

@ -1231,10 +1231,12 @@ class _Interpreter:
if (self.break_reporter):
self.break_reporter(prog.node_name, res, prog.line_number)
return res
except StopException:
raise
except:
self.error(_("Unknown field '{0}'").format(name), prog.line_number)
except (StopException, ValueError) as e:
raise e
except (StopException, ValueError):
raise
except:
self.error(_("Unknown field '{0}'").format('internal parse error'),
prog.line_number)
@ -1690,6 +1692,8 @@ class TemplateFormatter(string.Formatter):
formatter=self,
funcs=self._caller)
rslt = compiled_template(self.book, self.python_context_object)
except StopException:
raise
except Exception as e:
stack = traceback.extract_tb(exc_info()[2])
ss = stack[-1]