mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Merge branch 'StoredTemplates'
This commit is contained in:
commit
09b56dd006
@ -935,6 +935,12 @@ class ActionTemplateTester(InterfaceActionBase):
|
||||
description = _('Show an editor for testing templates')
|
||||
|
||||
|
||||
class ActionStoredTemplates(InterfaceActionBase):
|
||||
name = 'Stored Templates'
|
||||
actual_plugin = 'calibre.gui2.actions.show_stored_templates:ShowStoredTemplatesAction'
|
||||
description = _('Show a dialog for creating and managing stored templates')
|
||||
|
||||
|
||||
class ActionSaveToDisk(InterfaceActionBase):
|
||||
name = 'Save To Disk'
|
||||
actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction'
|
||||
@ -1099,7 +1105,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
||||
ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore,
|
||||
ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy,
|
||||
ActionMarkBooks, ActionEmbed, ActionTemplateTester, ActionTagMapper, ActionAuthorMapper,
|
||||
ActionVirtualLibrary, ActionBrowseAnnotations]
|
||||
ActionVirtualLibrary, ActionBrowseAnnotations, ActionStoredTemplates]
|
||||
|
||||
# }}}
|
||||
|
||||
@ -1272,6 +1278,18 @@ class TemplateFunctions(PreferencesPlugin):
|
||||
description = _('Create your own template functions')
|
||||
|
||||
|
||||
class StoredTemplates(PreferencesPlugin):
|
||||
name = 'StoredTemplates'
|
||||
icon = I('template_funcs.png')
|
||||
gui_name = _('Stored templates')
|
||||
category = 'Advanced'
|
||||
gui_category = _('Advanced')
|
||||
category_order = 5
|
||||
name_order = 6
|
||||
config_widget = 'calibre.gui2.preferences.stored_templates'
|
||||
description = _('Create stored calibre templates')
|
||||
|
||||
|
||||
class Email(PreferencesPlugin):
|
||||
name = 'Email'
|
||||
icon = I('mail.png')
|
||||
@ -1376,7 +1394,7 @@ class Misc(PreferencesPlugin):
|
||||
plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
|
||||
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
|
||||
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions,
|
||||
MetadataSources, Keyboard, IgnoredDevices]
|
||||
StoredTemplates, MetadataSources, Keyboard, IgnoredDevices]
|
||||
|
||||
# }}}
|
||||
|
||||
|
30
src/calibre/gui2/actions/show_stored_templates.py
Normal file
30
src/calibre/gui2/actions/show_stored_templates.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
from calibre.gui2.preferences.main import Preferences
|
||||
|
||||
|
||||
class ShowStoredTemplatesAction(InterfaceAction):
|
||||
|
||||
name = 'Stored Template'
|
||||
action_spec = (_('Stored Templates'), 'debug.png', None, ())
|
||||
dont_add_to = frozenset(('context-menu-device',))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.previous_text = _('Manage stored templates')
|
||||
self.first_time = True
|
||||
self.qaction.triggered.connect(self.show_template_editor)
|
||||
|
||||
def show_template_editor(self, *args):
|
||||
d = Preferences(self.gui, initial_plugin=('Advanced', 'StoredTemplates'),
|
||||
close_after_initial=True)
|
||||
d.exec_()
|
||||
|
@ -429,12 +429,17 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
name = unicode_type(toWhat)
|
||||
self.source_code.clear()
|
||||
self.documentation.clear()
|
||||
self.func_type.clear()
|
||||
if name in self.funcs:
|
||||
self.documentation.setPlainText(self.funcs[name].doc)
|
||||
if name in self.builtins and name in self.builtin_source_dict:
|
||||
self.source_code.setPlainText(self.builtin_source_dict[name])
|
||||
else:
|
||||
self.source_code.setPlainText(self.funcs[name].program_text)
|
||||
if self.funcs[name].is_python:
|
||||
self.func_type.setText(_('Template function in python'))
|
||||
else:
|
||||
self.func_type.setText(_('Stored template'))
|
||||
|
||||
def accept(self):
|
||||
txt = unicode_type(self.textbox.toPlainText()).rstrip()
|
||||
|
@ -224,6 +224,26 @@
|
||||
<widget class="QComboBox" name="function"/>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>&Function type:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>func_type</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="func_type">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Documentation:</string>
|
||||
@ -236,10 +256,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Python &code:</string>
|
||||
<string>&Code:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
@ -249,7 +269,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QPlainTextEdit" name="documentation">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
@ -259,17 +279,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QPlainTextEdit" name="source_code"/>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QLabel" name="template_tutorial">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QLabel" name="template_func_reference">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
|
212
src/calibre/gui2/preferences/stored_templates.py
Normal file
212
src/calibre/gui2/preferences/stored_templates.py
Normal file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import traceback
|
||||
|
||||
from PyQt5.Qt import (Qt, QGridLayout, QLabel, QSpacerItem, QSizePolicy,
|
||||
QComboBox, QTextEdit, QHBoxLayout, QPushButton)
|
||||
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||
from calibre.utils.formatter_functions import (formatter_functions,
|
||||
compile_user_function, compile_user_template_functions,
|
||||
load_user_template_functions, function_pref_is_python,
|
||||
function_pref_name)
|
||||
from polyglot.builtins import native_string_type, unicode_type
|
||||
|
||||
|
||||
class ConfigWidget(ConfigWidgetBase):
|
||||
|
||||
def genesis(self, gui):
|
||||
self.gui = gui
|
||||
self.db = gui.library_view.model().db
|
||||
|
||||
help_text = '<p>' + _('''
|
||||
Here you can add and remove stored templates used in template processing.
|
||||
You use a stored template in another template with the 'call' template
|
||||
function, as in 'call(somename, arguments...). Stored templates must use
|
||||
General Program Mode -- they must begin with the text 'program:'.
|
||||
In the stored template you get the arguments using the 'arguments()'
|
||||
template function, as in arguments(var1, var2, ...). The calling arguments
|
||||
are copied to the named variables.
|
||||
''') + '</p>'
|
||||
l = QGridLayout(self)
|
||||
w = QLabel(help_text)
|
||||
w.setWordWrap(True)
|
||||
l.addWidget(w, 0, 0, 1, 2)
|
||||
|
||||
lab = QLabel(_('&Template'))
|
||||
l.addWidget(lab, 1, 0)
|
||||
lb = QHBoxLayout()
|
||||
self.program = w = TemplateLineEditor(self)
|
||||
w.setPlaceholderText(_('The GPM template.'))
|
||||
lab.setBuddy(w)
|
||||
lb.addWidget(w, stretch=1)
|
||||
self.editor_button = b = QPushButton(_('&Open Editor'))
|
||||
b.clicked.connect(w.open_editor)
|
||||
lb.addWidget(b)
|
||||
l.addLayout(lb, 1, 1)
|
||||
|
||||
lab = QLabel(_('&Name'))
|
||||
l.addWidget(lab, 2, 0)
|
||||
self.function_name = w = QComboBox(self)
|
||||
w.setEditable(True)
|
||||
lab.setBuddy(w)
|
||||
w.setToolTip(_('The name of the function, used in a call statement'))
|
||||
|
||||
l.addWidget(w, 2, 1)
|
||||
|
||||
lab = QLabel(_('&Documentation'))
|
||||
l.addWidget(lab, 3, 0, Qt.AlignTop)
|
||||
self.documentation = w = QTextEdit(self)
|
||||
w.setPlaceholderText(_('A description of the template. Whatever you wish ...'))
|
||||
lab.setBuddy(w)
|
||||
l.addWidget(w, 3, 1)
|
||||
|
||||
lb = QHBoxLayout()
|
||||
lb.addStretch(1)
|
||||
self.clear_button = w = QPushButton(_('C&lear'))
|
||||
lb.addWidget(w)
|
||||
self.delete_button = w = QPushButton(_('&Delete'))
|
||||
lb.addWidget(w)
|
||||
self.replace_button = w = QPushButton(_('&Replace'))
|
||||
lb.addWidget(w)
|
||||
self.create_button = w = QPushButton(_('&Create'))
|
||||
lb.addWidget(w)
|
||||
lb.addStretch(1)
|
||||
l.addLayout(lb, 9, 1)
|
||||
|
||||
l.addItem(QSpacerItem(10, 10, vPolicy=QSizePolicy.Expanding), 10, 0, -1, -1)
|
||||
self.setLayout(l)
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.funcs = {}
|
||||
for v in self.db.prefs.get('user_template_functions', []):
|
||||
if not function_pref_is_python(v):
|
||||
self.funcs.update({function_pref_name(v):compile_user_function(*v)})
|
||||
|
||||
self.build_function_names_box()
|
||||
self.function_name.currentIndexChanged[native_string_type].connect(self.function_index_changed)
|
||||
self.function_name.editTextChanged.connect(self.function_name_edited)
|
||||
self.documentation.textChanged.connect(self.enable_replace_button)
|
||||
self.program.textChanged.connect(self.enable_replace_button)
|
||||
self.create_button.clicked.connect(self.create_button_clicked)
|
||||
self.delete_button.clicked.connect(self.delete_button_clicked)
|
||||
self.create_button.setEnabled(False)
|
||||
self.delete_button.setEnabled(False)
|
||||
self.replace_button.setEnabled(False)
|
||||
self.clear_button.clicked.connect(self.clear_button_clicked)
|
||||
self.replace_button.clicked.connect(self.replace_button_clicked)
|
||||
|
||||
def enable_replace_button(self):
|
||||
self.replace_button.setEnabled(self.delete_button.isEnabled())
|
||||
|
||||
def clear_button_clicked(self):
|
||||
self.build_function_names_box()
|
||||
self.program.clear()
|
||||
self.documentation.clear()
|
||||
self.create_button.setEnabled(False)
|
||||
self.delete_button.setEnabled(False)
|
||||
|
||||
def build_function_names_box(self, scroll_to=''):
|
||||
self.function_name.blockSignals(True)
|
||||
func_names = sorted(self.funcs)
|
||||
self.function_name.clear()
|
||||
self.function_name.addItem('')
|
||||
self.function_name.addItems(func_names)
|
||||
self.function_name.setCurrentIndex(0)
|
||||
self.function_name.blockSignals(False)
|
||||
if scroll_to:
|
||||
idx = self.function_name.findText(scroll_to)
|
||||
if idx >= 0:
|
||||
self.function_name.setCurrentIndex(idx)
|
||||
|
||||
def delete_button_clicked(self):
|
||||
name = unicode_type(self.function_name.currentText())
|
||||
if name in self.funcs:
|
||||
del self.funcs[name]
|
||||
self.changed_signal.emit()
|
||||
self.create_button.setEnabled(True)
|
||||
self.delete_button.setEnabled(False)
|
||||
self.build_function_names_box()
|
||||
self.program.setReadOnly(False)
|
||||
else:
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('Function not defined'), show=True)
|
||||
|
||||
def create_button_clicked(self, use_name=None):
|
||||
self.changed_signal.emit()
|
||||
name = use_name if use_name else unicode_type(self.function_name.currentText())
|
||||
for k,v in formatter_functions().get_functions().items():
|
||||
if k == name and v.is_python:
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('Name %s is already used for template function')%(name,), show=True)
|
||||
try:
|
||||
prog = unicode_type(self.program.text())
|
||||
if not prog.startswith('program:'):
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('The stored template must begin with "program:"'), show=True)
|
||||
|
||||
cls = compile_user_function(name, unicode_type(self.documentation.toPlainText()),
|
||||
0, prog)
|
||||
self.funcs[name] = cls
|
||||
self.build_function_names_box(scroll_to=name)
|
||||
except:
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('Exception while storing template'), show=True,
|
||||
det_msg=traceback.format_exc())
|
||||
|
||||
def function_name_edited(self, txt):
|
||||
self.documentation.setReadOnly(False)
|
||||
self.create_button.setEnabled(True)
|
||||
self.replace_button.setEnabled(False)
|
||||
self.program.setReadOnly(False)
|
||||
|
||||
def function_index_changed(self, txt):
|
||||
txt = unicode_type(txt)
|
||||
self.create_button.setEnabled(False)
|
||||
if not txt:
|
||||
self.program.clear()
|
||||
self.documentation.clear()
|
||||
self.documentation.setReadOnly(False)
|
||||
return
|
||||
func = self.funcs[txt]
|
||||
self.documentation.setText(func.doc)
|
||||
self.program.setText(func.program_text)
|
||||
self.delete_button.setEnabled(True)
|
||||
self.program.setReadOnly(False)
|
||||
self.replace_button.setEnabled(False)
|
||||
|
||||
def replace_button_clicked(self):
|
||||
name = unicode_type(self.function_name.currentText())
|
||||
self.delete_button_clicked()
|
||||
self.create_button_clicked(use_name=name)
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
pass
|
||||
|
||||
def commit(self):
|
||||
# formatter_functions().reset_to_builtins()
|
||||
pref_value = [v for v in self.db.prefs.get('user_template_functions', [])
|
||||
if function_pref_is_python(v)]
|
||||
for v in self.funcs.values():
|
||||
pref_value.append(v.to_pref())
|
||||
self.db.new_api.set_pref('user_template_functions', pref_value)
|
||||
funcs = compile_user_template_functions(pref_value)
|
||||
self.db.new_api.set_user_template_functions(funcs)
|
||||
self.gui.library_view.model().refresh()
|
||||
load_user_template_functions(self.db.library_id, [], funcs)
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.Qt import QApplication
|
||||
app = QApplication([])
|
||||
test_widget('Advanced', 'StoredTemplates')
|
@ -16,7 +16,8 @@ from calibre.gui2.preferences.template_functions_ui import Ui_Form
|
||||
from calibre.gui2.widgets import PythonHighlighter
|
||||
from calibre.utils.formatter_functions import (formatter_functions,
|
||||
compile_user_function, compile_user_template_functions,
|
||||
load_user_template_functions)
|
||||
load_user_template_functions, function_pref_is_python,
|
||||
function_pref_name)
|
||||
from polyglot.builtins import iteritems, native_string_type, unicode_type
|
||||
|
||||
|
||||
@ -86,7 +87,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
traceback.print_exc()
|
||||
self.builtin_source_dict = {}
|
||||
|
||||
self.funcs = formatter_functions().get_functions()
|
||||
self.funcs = dict((k,v) for k,v in formatter_functions().get_functions().items()
|
||||
if v.is_python)
|
||||
|
||||
self.builtins = formatter_functions().get_builtins_and_aliases()
|
||||
|
||||
self.build_function_names_box()
|
||||
@ -116,16 +119,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.create_button.setEnabled(False)
|
||||
self.delete_button.setEnabled(False)
|
||||
|
||||
def build_function_names_box(self, scroll_to='', set_to=''):
|
||||
def build_function_names_box(self, scroll_to=''):
|
||||
self.function_name.blockSignals(True)
|
||||
func_names = sorted(self.funcs)
|
||||
self.function_name.clear()
|
||||
self.function_name.addItem('')
|
||||
self.function_name.addItems(func_names)
|
||||
self.function_name.setCurrentIndex(0)
|
||||
if set_to:
|
||||
self.function_name.setEditText(set_to)
|
||||
self.create_button.setEnabled(True)
|
||||
self.function_name.blockSignals(False)
|
||||
if scroll_to:
|
||||
idx = self.function_name.findText(scroll_to)
|
||||
@ -140,23 +140,30 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
error_dialog(self.gui, _('Template functions'),
|
||||
_('You cannot delete a built-in function'), show=True)
|
||||
if name in self.funcs:
|
||||
print('delete')
|
||||
del self.funcs[name]
|
||||
self.changed_signal.emit()
|
||||
self.create_button.setEnabled(True)
|
||||
self.delete_button.setEnabled(False)
|
||||
self.build_function_names_box(set_to=name)
|
||||
self.build_function_names_box()
|
||||
self.program.setReadOnly(False)
|
||||
else:
|
||||
error_dialog(self.gui, _('Template functions'),
|
||||
_('Function not defined'), show=True)
|
||||
|
||||
def create_button_clicked(self):
|
||||
def create_button_clicked(self, use_name=None):
|
||||
self.changed_signal.emit()
|
||||
name = unicode_type(self.function_name.currentText())
|
||||
name = use_name if use_name else unicode_type(self.function_name.currentText())
|
||||
if name in self.funcs:
|
||||
error_dialog(self.gui, _('Template functions'),
|
||||
_('Name %s already used')%(name,), show=True)
|
||||
return
|
||||
if name in {function_pref_name(v) for v in
|
||||
self.db.prefs.get('user_template_functions', [])
|
||||
if not function_pref_is_python(v)}:
|
||||
error_dialog(self.gui, _('Template functions'),
|
||||
_('Name %s is already used for stored template')%(name,), show=True)
|
||||
return
|
||||
if self.argument_count.value() == 0:
|
||||
box = warning_dialog(self.gui, _('Template functions'),
|
||||
_('Argument count should be -1 or greater than zero. '
|
||||
@ -215,18 +222,19 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.replace_button.setEnabled(False)
|
||||
|
||||
def replace_button_clicked(self):
|
||||
name = unicode_type(self.function_name.currentText())
|
||||
self.delete_button_clicked()
|
||||
self.create_button_clicked()
|
||||
self.create_button_clicked(use_name=name)
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
pass
|
||||
|
||||
def commit(self):
|
||||
# formatter_functions().reset_to_builtins()
|
||||
pref_value = []
|
||||
pref_value = [v for v in self.db.prefs.get('user_template_functions', [])
|
||||
if not function_pref_is_python(v)]
|
||||
for name, cls in iteritems(self.funcs):
|
||||
if name not in self.builtins:
|
||||
pref_value.append((cls.name, cls.doc, cls.arg_count, cls.program_text))
|
||||
pref_value.append(cls.to_pref())
|
||||
self.db.new_api.set_pref('user_template_functions', pref_value)
|
||||
funcs = compile_user_template_functions(pref_value)
|
||||
self.db.new_api.set_user_template_functions(funcs)
|
||||
|
@ -28,6 +28,8 @@ class Node(object):
|
||||
NODE_CONSTANT = 7
|
||||
NODE_FIELD = 8
|
||||
NODE_RAW_FIELD = 9
|
||||
NODE_CALL = 10
|
||||
NODE_ARGUMENTS = 11
|
||||
|
||||
|
||||
class IfNode(Node):
|
||||
@ -55,6 +57,21 @@ class FunctionNode(Node):
|
||||
self.expression_list = expression_list
|
||||
|
||||
|
||||
class CallNode(Node):
|
||||
def __init__(self, function, expression_list):
|
||||
Node.__init__(self)
|
||||
self.node_type = self.NODE_CALL
|
||||
self.function = function
|
||||
self.expression_list = expression_list
|
||||
|
||||
|
||||
class ArgumentsNode(Node):
|
||||
def __init__(self, expression_list):
|
||||
Node.__init__(self)
|
||||
self.node_type = self.NODE_ARGUMENTS
|
||||
self.expression_list = expression_list
|
||||
|
||||
|
||||
class StringInfixNode(Node):
|
||||
def __init__(self, operator, left, right):
|
||||
Node.__init__(self)
|
||||
@ -188,6 +205,13 @@ class _Parser(object):
|
||||
except:
|
||||
return False
|
||||
|
||||
def token_is_call(self):
|
||||
try:
|
||||
token = self.prog[self.lex_pos]
|
||||
return token[1] == 'call' and token[0] == self.LEX_KEYWORD
|
||||
except:
|
||||
return False
|
||||
|
||||
def token_is_if(self):
|
||||
try:
|
||||
token = self.prog[self.lex_pos]
|
||||
@ -228,9 +252,11 @@ class _Parser(object):
|
||||
except:
|
||||
return True
|
||||
|
||||
def program(self, funcs, prog):
|
||||
def program(self, parent, funcs, prog):
|
||||
self.lex_pos = 0
|
||||
self.parent = parent
|
||||
self.funcs = funcs
|
||||
self.func_names = frozenset(set(self.funcs.keys()) | {'arguments',})
|
||||
self.prog = prog[0]
|
||||
self.prog_len = len(self.prog)
|
||||
if prog[1] != '':
|
||||
@ -276,9 +302,37 @@ class _Parser(object):
|
||||
return NumericInfixNode(operator, left, self.expr())
|
||||
return left
|
||||
|
||||
def call_expression(self):
|
||||
self.consume()
|
||||
if not self.token_is_id():
|
||||
self.error(_('"call" requires a stored template name'))
|
||||
name = self.token()
|
||||
if name not in self.func_names or self.funcs[name].is_python:
|
||||
self.error(_('{} is not a stored template').format(name))
|
||||
text = self.funcs[name].program_text
|
||||
if not text.startswith('program:'):
|
||||
self.error((_('A stored template must begin with program:')))
|
||||
text = text[len('program:'):]
|
||||
if not self.token_op_is_lparen():
|
||||
self.error(_('"call" requires arguments surrounded by "(" ")"'))
|
||||
self.consume()
|
||||
arguments = list()
|
||||
while not self.token_op_is_rparen():
|
||||
arguments.append(self.infix_expr())
|
||||
if not self.token_op_is_comma():
|
||||
break
|
||||
self.consume()
|
||||
if self.token() != ')':
|
||||
self.error(_('Missing closing parenthesis'))
|
||||
subprog = _Parser().program(self, self.funcs,
|
||||
self.parent.lex_scanner.scan(text))
|
||||
return CallNode(subprog, arguments)
|
||||
|
||||
def expr(self):
|
||||
if self.token_is_if():
|
||||
return self.if_expression()
|
||||
if self.token_is_call():
|
||||
return self.call_expression()
|
||||
if self.token_is_id():
|
||||
# We have an identifier. Determine if it is a function
|
||||
id_ = self.token()
|
||||
@ -293,7 +347,7 @@ class _Parser(object):
|
||||
# Check if it is a known one. We do this here so error reporting is
|
||||
# better, as it can identify the tokens near the problem.
|
||||
id_ = id_.strip()
|
||||
if id_ not in self.funcs:
|
||||
if id_ not in self.func_names:
|
||||
self.error(_('Unknown function {0}').format(id_))
|
||||
# Eat the paren
|
||||
self.consume()
|
||||
@ -314,9 +368,19 @@ class _Parser(object):
|
||||
return IfNode(arguments[0], (arguments[1],), (arguments[2],))
|
||||
if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE):
|
||||
return AssignNode(arguments[0].name, arguments[1])
|
||||
if id_ == 'arguments':
|
||||
new_args = []
|
||||
for arg in arguments:
|
||||
if arg.node_type not in (Node.NODE_ASSIGN, Node.NODE_RVALUE):
|
||||
self.error(_("Parameters to 'arguments' must be "
|
||||
"variables or assignments"))
|
||||
if arg.node_type == Node.NODE_RVALUE:
|
||||
arg = AssignNode(arg.name, ConstantNode(''))
|
||||
new_args.append(arg)
|
||||
return ArgumentsNode(new_args)
|
||||
cls = self.funcs[id_]
|
||||
if cls.arg_count != -1 and len(arguments) != cls.arg_count:
|
||||
self.error(_('Incorrect number of expression_list for function {0}').format(id_))
|
||||
self.error(_('Incorrect number of arguments for function {0}').format(id_))
|
||||
return FunctionNode(id_, arguments)
|
||||
elif self.token_is_constant():
|
||||
# String or number
|
||||
@ -408,6 +472,24 @@ class _Interpreter(object):
|
||||
return cls.eval_(self.parent, self.parent_kwargs,
|
||||
self.parent_book, self.locals, *args)
|
||||
|
||||
def do_node_call(self, prog):
|
||||
args = list()
|
||||
for arg in prog.expression_list:
|
||||
# evaluate the expression (recursive call)
|
||||
args.append(self.expr(arg))
|
||||
saved_locals = self.locals
|
||||
self.locals = {}
|
||||
for dex, v in enumerate(args):
|
||||
self.locals['*arg_'+ str(dex)] = v
|
||||
val = self.expression_list(prog.function)
|
||||
self.locals = saved_locals
|
||||
return val
|
||||
|
||||
def do_node_arguments(self, prog):
|
||||
for dex,arg in enumerate(prog.expression_list):
|
||||
self.locals[arg.left] = self.locals.get('*arg_'+ str(dex), self.expr(arg.right))
|
||||
return ''
|
||||
|
||||
def do_node_constant(self, prog):
|
||||
return prog.value
|
||||
|
||||
@ -454,6 +536,8 @@ class _Interpreter(object):
|
||||
Node.NODE_RAW_FIELD: do_node_raw_field,
|
||||
Node.NODE_STRING_INFIX: do_node_string_infix,
|
||||
Node.NODE_NUMERIC_INFIX: do_node_numeric_infix,
|
||||
Node.NODE_ARGUMENTS: do_node_arguments,
|
||||
Node.NODE_CALL: do_node_call,
|
||||
}
|
||||
|
||||
def expr(self, prog):
|
||||
@ -536,7 +620,7 @@ class TemplateFormatter(string.Formatter):
|
||||
lex_scanner = re.Scanner([
|
||||
(r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)),
|
||||
(r'(==|!=|<=|<|>=|>)', lambda x,t: (_Parser.LEX_STRING_INFIX, t)), # noqa
|
||||
(r'(if|then|else|fi)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
||||
(r'(if|then|else|fi|call)\b',lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
||||
(r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa
|
||||
(r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa
|
||||
(r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
||||
@ -554,10 +638,10 @@ class TemplateFormatter(string.Formatter):
|
||||
if column_name is not None and self.template_cache is not None:
|
||||
tree = self.template_cache.get(column_name, None)
|
||||
if not tree:
|
||||
tree = self.gpm_parser.program(self.funcs, self.lex_scanner.scan(prog))
|
||||
tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
|
||||
self.template_cache[column_name] = tree
|
||||
else:
|
||||
tree = self.gpm_parser.program(self.funcs, self.lex_scanner.scan(prog))
|
||||
tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
|
||||
return self.gpm_interpreter.program(self.funcs, self, tree, val)
|
||||
|
||||
# ################# Override parent classes methods #####################
|
||||
|
@ -126,6 +126,7 @@ class FormatterFunction(object):
|
||||
category = 'Unknown'
|
||||
arg_count = 0
|
||||
aliases = []
|
||||
is_python = True
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
raise NotImplementedError()
|
||||
@ -1813,17 +1814,35 @@ _formatter_builtins = [
|
||||
|
||||
class FormatterUserFunction(FormatterFunction):
|
||||
|
||||
def __init__(self, name, doc, arg_count, program_text):
|
||||
def __init__(self, name, doc, arg_count, program_text, is_python):
|
||||
self.is_python = is_python
|
||||
self.name = name
|
||||
self.doc = doc
|
||||
self.arg_count = arg_count
|
||||
self.program_text = program_text
|
||||
|
||||
def to_pref(self):
|
||||
return [self.name, self.doc, self.arg_count, self.program_text]
|
||||
|
||||
tabs = re.compile(r'^\t*')
|
||||
|
||||
|
||||
def function_pref_is_python(pref):
|
||||
if isinstance(pref, list):
|
||||
pref = pref[3]
|
||||
if pref.startswith('def'):
|
||||
return True
|
||||
if pref.startswith('program'):
|
||||
return False
|
||||
raise ValueError('Unknown program type in formatter function pref')
|
||||
|
||||
def function_pref_name(pref):
|
||||
return pref[0]
|
||||
|
||||
def compile_user_function(name, doc, arg_count, eval_func):
|
||||
if not function_pref_is_python(eval_func):
|
||||
return FormatterUserFunction(name, doc, arg_count, eval_func, False)
|
||||
|
||||
def replace_func(mo):
|
||||
return mo.group().replace('\t', ' ')
|
||||
|
||||
@ -1838,7 +1857,7 @@ class UserFunction(FormatterUserFunction):
|
||||
if DEBUG and tweaks.get('enable_template_debug_printing', False):
|
||||
print(prog)
|
||||
exec(prog, locals_)
|
||||
cls = locals_['UserFunction'](name, doc, arg_count, eval_func)
|
||||
cls = locals_['UserFunction'](name, doc, arg_count, eval_func, True)
|
||||
return cls
|
||||
|
||||
|
||||
@ -1855,6 +1874,7 @@ def compile_user_template_functions(funcs):
|
||||
# then white space differences don't cause them to compare differently
|
||||
|
||||
cls = compile_user_function(*func)
|
||||
cls.is_python = function_pref_is_python(func)
|
||||
compiled_funcs[cls.name] = cls
|
||||
except Exception:
|
||||
try:
|
||||
@ -1862,7 +1882,7 @@ def compile_user_template_functions(funcs):
|
||||
except Exception:
|
||||
func_name = 'Unknown'
|
||||
prints('**** Compilation errors in user template function "%s" ****' % func_name)
|
||||
traceback.print_exc(limit=0)
|
||||
traceback.print_exc(limit=10)
|
||||
prints('**** End compilation errors in %s "****"' % func_name)
|
||||
return compiled_funcs
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user