mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05:00 
			
		
		
		
	Several related changes:
* Add a "with" statement to the template language that for the duration of the code block changes the "current book" to the one specified by the book id.
* A new formatter function selected_books() that returns the book ids of the currently selected books
* A new formatter function selected_column() that returns the lookup name of the column containing the selected cell.
* A new formatter function sort_book_ids() that sorts the books specified by book_ids.
* A new formatter function show_dialog() that opens a dialog to display plain text or html.
* Add check boxes to the template tester to control "run as you type" and to restrict test runs to the first selected book.
Here is an example using several of the new features:
program:
 ids = sort_book_ids(selected_books(), 'series', 1, 'title', 1);
 res = '<style> th, td {padding: 2px;}</style> <h2>Book Size Report</h2><p><table>';
 total = 0;
 def table_row(title, series, size):
  return strcat('<tr><td>', title, '</td>',
       '<td>', series, '</td>',
       '<td>', if size !=# 0 then human_readable(size) else '0' fi, '</td>',
       '</tr>', character('newline'))
 fed;
 for id in ids:
  with id:
   s = booksize();
   total = total + s;
   res = strcat(res, table_row($title, $series, s))
  htiw
 rof;
 res = strcat(res, table_row('TOTAL', '', total));
 res = strcat(res, '</table>');
 show_dialog(res)
			
			
This commit is contained in:
		
							parent
							
								
									91216de5f3
								
							
						
					
					
						commit
						ed83d9eeb8
					
				@ -488,6 +488,8 @@ def create_defs():
 | 
				
			|||||||
    defs['book_details_note_link_icon_width'] = 1.0
 | 
					    defs['book_details_note_link_icon_width'] = 1.0
 | 
				
			||||||
    defs['tag_browser_show_category_icons'] = True
 | 
					    defs['tag_browser_show_category_icons'] = True
 | 
				
			||||||
    defs['tag_browser_show_value_icons'] = True
 | 
					    defs['tag_browser_show_value_icons'] = True
 | 
				
			||||||
 | 
					    defs['template_editor_run_as_you_type'] = True
 | 
				
			||||||
 | 
					    defs['template_editor_show_all_selected_books'] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def migrate_tweak(tweak_name, pref_name):
 | 
					    def migrate_tweak(tweak_name, pref_name):
 | 
				
			||||||
        # If the tweak has been changed then leave the tweak in the file so
 | 
					        # If the tweak has been changed then leave the tweak in the file so
 | 
				
			||||||
 | 
				
			|||||||
@ -212,7 +212,7 @@ class TemplateHighlighter(QSyntaxHighlighter):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    KEYWORDS_GPM = ['if', 'then', 'else', 'elif', 'fi', 'for', 'rof',
 | 
					    KEYWORDS_GPM = ['if', 'then', 'else', 'elif', 'fi', 'for', 'rof',
 | 
				
			||||||
                    'separator', 'break', 'continue', 'return', 'in', 'inlist',
 | 
					                    'separator', 'break', 'continue', 'return', 'in', 'inlist',
 | 
				
			||||||
                    'inlist_field', 'def', 'fed', 'limit']
 | 
					                    'inlist_field', 'def', 'fed', 'limit', 'with', 'htiw']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    KEYWORDS_PYTHON = ['and', 'as', 'assert', 'break', 'class', 'continue', 'def',
 | 
					    KEYWORDS_PYTHON = ['and', 'as', 'assert', 'break', 'class', 'continue', 'def',
 | 
				
			||||||
                       'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
 | 
					                       'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
 | 
				
			||||||
@ -581,8 +581,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
                         formatter_functions().get_builtins_and_aliases())
 | 
					                         formatter_functions().get_builtins_and_aliases())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Set up the breakpoint bar
 | 
					        # Set up the breakpoint bar
 | 
				
			||||||
        s = gprefs.get('template_editor_break_on_print', False)
 | 
					        run_as_you_type = gprefs.get('template_editor_run_as_you_type')
 | 
				
			||||||
        self.go_button.setEnabled(s)
 | 
					        self.run_as_you_type_box.setChecked(run_as_you_type)
 | 
				
			||||||
 | 
					        self.go_button.setEnabled(not run_as_you_type)
 | 
				
			||||||
 | 
					        self.break_box.setEnabled(not run_as_you_type)
 | 
				
			||||||
 | 
					        s = gprefs.get('template_editor_enable_breakpoints', False)
 | 
				
			||||||
        self.remove_all_button.setEnabled(s)
 | 
					        self.remove_all_button.setEnabled(s)
 | 
				
			||||||
        self.set_all_button.setEnabled(s)
 | 
					        self.set_all_button.setEnabled(s)
 | 
				
			||||||
        self.toggle_button.setEnabled(s)
 | 
					        self.toggle_button.setEnabled(s)
 | 
				
			||||||
@ -591,6 +594,10 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        self.break_box.setChecked(s)
 | 
					        self.break_box.setChecked(s)
 | 
				
			||||||
        self.break_box.stateChanged.connect(self.break_box_changed)
 | 
					        self.break_box.stateChanged.connect(self.break_box_changed)
 | 
				
			||||||
        self.go_button.clicked.connect(self.go_button_pressed)
 | 
					        self.go_button.clicked.connect(self.go_button_pressed)
 | 
				
			||||||
 | 
					        self.show_all_selected_books.clicked.connect(self.show_all_selected_books_changed)
 | 
				
			||||||
 | 
					        self.run_as_you_type_box.stateChanged.connect(self.run_as_you_type_box_changed)
 | 
				
			||||||
 | 
					        self.show_all_selected_books.setChecked(gprefs.get('template_editor_show_all_selected_books'))
 | 
				
			||||||
 | 
					        self.show_all_selected_books.clicked.connect(self.show_all_selected_books_changed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Set up the display table
 | 
					        # Set up the display table
 | 
				
			||||||
        self.table_column_widths = None
 | 
					        self.table_column_widths = None
 | 
				
			||||||
@ -725,14 +732,20 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            mi = (get_model_metadata_instance(), )
 | 
					            mi = (get_model_metadata_instance(), )
 | 
				
			||||||
        self.mi = mi
 | 
					        self.mi = mi
 | 
				
			||||||
 | 
					        self.setup_result_display_table()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setup_result_display_table(self):
 | 
				
			||||||
        tv = self.template_value
 | 
					        tv = self.template_value
 | 
				
			||||||
 | 
					        mi = self.mi
 | 
				
			||||||
 | 
					        row_count = len(mi) if gprefs.get('template_editor_show_all_selected_books') else 1
 | 
				
			||||||
 | 
					        tv.clear()
 | 
				
			||||||
        tv.setColumnCount(3)
 | 
					        tv.setColumnCount(3)
 | 
				
			||||||
        tv.setHorizontalHeaderLabels((_('Book title'), '', _('Template value')))
 | 
					        tv.setHorizontalHeaderLabels((_('Book title'), '', _('Template value')))
 | 
				
			||||||
        tv.horizontalHeader().setStretchLastSection(True)
 | 
					        tv.horizontalHeader().setStretchLastSection(True)
 | 
				
			||||||
        tv.horizontalHeader().sectionResized.connect(self.table_column_resized)
 | 
					        tv.horizontalHeader().sectionResized.connect(self.table_column_resized)
 | 
				
			||||||
        tv.setRowCount(len(mi))
 | 
					        tv.setRowCount(len(mi) )
 | 
				
			||||||
        # Set the height of the table
 | 
					        # Set the height of the table
 | 
				
			||||||
        h = tv.rowHeight(0) * min(len(mi), 5)
 | 
					        h = tv.rowHeight(0) * min(row_count, 5)
 | 
				
			||||||
        h += 2 * tv.frameWidth() + tv.horizontalHeader().height()
 | 
					        h += 2 * tv.frameWidth() + tv.horizontalHeader().height()
 | 
				
			||||||
        tv.setMinimumHeight(h)
 | 
					        tv.setMinimumHeight(h)
 | 
				
			||||||
        tv.setMaximumHeight(h)
 | 
					        tv.setMaximumHeight(h)
 | 
				
			||||||
@ -742,9 +755,9 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            tv.setColumnWidth(0, tv.fontMetrics().averageCharWidth() * 10)
 | 
					            tv.setColumnWidth(0, tv.fontMetrics().averageCharWidth() * 10)
 | 
				
			||||||
        tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
 | 
					        tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
 | 
				
			||||||
        tv.setRowCount(len(mi))
 | 
					        tv.setRowCount(row_count)
 | 
				
			||||||
        # Use our own widget to get rid of elision. setTextElideMode() doesn't work
 | 
					        # Use our own widget to get rid of elision. setTextElideMode() doesn't work
 | 
				
			||||||
        for r in range(len(mi)):
 | 
					        for r in range(row_count):
 | 
				
			||||||
            w = QLineEdit(tv)
 | 
					            w = QLineEdit(tv)
 | 
				
			||||||
            w.setReadOnly(True)
 | 
					            w.setReadOnly(True)
 | 
				
			||||||
            w.setText(mi[r].get('title', _('No title provided')))
 | 
					            w.setText(mi[r].get('title', _('No title provided')))
 | 
				
			||||||
@ -786,15 +799,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
 | 
				
			|||||||
                    pmi = None
 | 
					                    pmi = None
 | 
				
			||||||
                new_mi.append(pmi)
 | 
					                new_mi.append(pmi)
 | 
				
			||||||
            self.set_mi(new_mi, self.fm)
 | 
					            self.set_mi(new_mi, self.fm)
 | 
				
			||||||
            if not self.break_box.isChecked():
 | 
					            if not self.run_as_you_type_box.isChecked():
 | 
				
			||||||
                self.display_values(str(self.textbox.toPlainText()))
 | 
					                self.display_values(str(self.textbox.toPlainText()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_waiting_message(self):
 | 
					    def set_waiting_message(self):
 | 
				
			||||||
        if self.break_box.isChecked():
 | 
					        if not self.run_as_you_type_box.isChecked():
 | 
				
			||||||
            for i in range(len(self.mi)):
 | 
					            for i in range(self.template_value.rowCount()):
 | 
				
			||||||
                self.template_value.cellWidget(i, 2).setText('')
 | 
					                self.template_value.cellWidget(i, 2).setText('')
 | 
				
			||||||
            self.template_value.cellWidget(0, 2).setText(
 | 
					            self.template_value.cellWidget(0, 2).setText(
 | 
				
			||||||
                _("*** Breakpoints are enabled. Waiting for the 'Go' button to be pressed"))
 | 
					                _("*** Waiting for the 'Go' button to be pressed"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_code_context_menu(self, point):
 | 
					    def show_code_context_menu(self, point):
 | 
				
			||||||
        m = self.source_code.createStandardContextMenu()
 | 
					        m = self.source_code.createStandardContextMenu()
 | 
				
			||||||
@ -930,15 +943,40 @@ def evaluate(book, context):
 | 
				
			|||||||
        gprefs['gpm_template_editor_font_size'] = toWhat
 | 
					        gprefs['gpm_template_editor_font_size'] = toWhat
 | 
				
			||||||
        self.set_editor_font()
 | 
					        self.set_editor_font()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run_as_you_type_box_changed(self, new_state):
 | 
				
			||||||
 | 
					        gprefs['template_editor_run_as_you_type'] = new_state != 0
 | 
				
			||||||
 | 
					        self.go_button.setEnabled(new_state == 0)
 | 
				
			||||||
 | 
					        if new_state == 0:
 | 
				
			||||||
 | 
					            self.set_waiting_message()
 | 
				
			||||||
 | 
					            self.break_box.setEnabled(True)
 | 
				
			||||||
 | 
					            enable_break_boxes = self.break_box.isChecked()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.break_box.setEnabled(False)
 | 
				
			||||||
 | 
					            enable_break_boxes = False
 | 
				
			||||||
 | 
					            self.display_values(str(self.textbox.toPlainText()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.remove_all_button.setEnabled(enable_break_boxes)
 | 
				
			||||||
 | 
					        self.set_all_button.setEnabled(enable_break_boxes)
 | 
				
			||||||
 | 
					        self.toggle_button.setEnabled(enable_break_boxes)
 | 
				
			||||||
 | 
					        self.breakpoint_line_box.setEnabled(enable_break_boxes)
 | 
				
			||||||
 | 
					        self.breakpoint_line_box_label.setEnabled(enable_break_boxes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def break_box_changed(self, new_state):
 | 
					    def break_box_changed(self, new_state):
 | 
				
			||||||
        gprefs['template_editor_break_on_print'] = new_state != 0
 | 
					        gprefs['template_editor_enable_breakpoints'] = new_state != 0
 | 
				
			||||||
        self.go_button.setEnabled(new_state != 0)
 | 
					 | 
				
			||||||
        self.remove_all_button.setEnabled(new_state != 0)
 | 
					        self.remove_all_button.setEnabled(new_state != 0)
 | 
				
			||||||
        self.set_all_button.setEnabled(new_state != 0)
 | 
					        self.set_all_button.setEnabled(new_state != 0)
 | 
				
			||||||
        self.toggle_button.setEnabled(new_state != 0)
 | 
					        self.toggle_button.setEnabled(new_state != 0)
 | 
				
			||||||
        self.breakpoint_line_box.setEnabled(new_state != 0)
 | 
					        self.breakpoint_line_box.setEnabled(new_state != 0)
 | 
				
			||||||
        self.breakpoint_line_box_label.setEnabled(new_state != 0)
 | 
					        self.breakpoint_line_box_label.setEnabled(new_state != 0)
 | 
				
			||||||
        if new_state == 0:
 | 
					        if gprefs['template_editor_run_as_you_type']:
 | 
				
			||||||
 | 
					            self.display_values(str(self.textbox.toPlainText()))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.set_waiting_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def show_all_selected_books_changed(self, new_state):
 | 
				
			||||||
 | 
					        gprefs['template_editor_show_all_selected_books'] = new_state != 0
 | 
				
			||||||
 | 
					        self.setup_result_display_table()
 | 
				
			||||||
 | 
					        if gprefs['template_editor_run_as_you_type']:
 | 
				
			||||||
            self.display_values(str(self.textbox.toPlainText()))
 | 
					            self.display_values(str(self.textbox.toPlainText()))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.set_waiting_message()
 | 
					            self.set_waiting_message()
 | 
				
			||||||
@ -1042,7 +1080,7 @@ def evaluate(book, context):
 | 
				
			|||||||
            self.last_text = cur_text
 | 
					            self.last_text = cur_text
 | 
				
			||||||
            self.highlighter.regenerate_paren_positions()
 | 
					            self.highlighter.regenerate_paren_positions()
 | 
				
			||||||
            self.text_cursor_changed()
 | 
					            self.text_cursor_changed()
 | 
				
			||||||
            if not self.break_box.isChecked():
 | 
					            if self.run_as_you_type_box.isChecked():
 | 
				
			||||||
                self.display_values(cur_text)
 | 
					                self.display_values(cur_text)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.set_waiting_message()
 | 
					                self.set_waiting_message()
 | 
				
			||||||
@ -1095,6 +1133,8 @@ def evaluate(book, context):
 | 
				
			|||||||
                w.setCursorPosition(0)
 | 
					                w.setCursorPosition(0)
 | 
				
			||||||
            finally:
 | 
					            finally:
 | 
				
			||||||
                sys.settrace(None)
 | 
					                sys.settrace(None)
 | 
				
			||||||
 | 
					            if not gprefs.get('template_editor_show_all_selected_books', True):
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def text_cursor_changed(self):
 | 
					    def text_cursor_changed(self):
 | 
				
			||||||
        cursor = self.textbox.textCursor()
 | 
					        cursor = self.textbox.textCursor()
 | 
				
			||||||
 | 
				
			|||||||
@ -206,14 +206,13 @@
 | 
				
			|||||||
            </widget>
 | 
					            </widget>
 | 
				
			||||||
           </item>
 | 
					           </item>
 | 
				
			||||||
           <item>
 | 
					           <item>
 | 
				
			||||||
            <widget class="QCheckBox" name="break_box">
 | 
					            <widget class="QCheckBox" name="run_as_you_type_box">
 | 
				
			||||||
             <property name="text">
 | 
					             <property name="text">
 | 
				
			||||||
              <string>Enable &breakpoints</string>
 | 
					              <string>R&un as you type</string>
 | 
				
			||||||
             </property>
 | 
					             </property>
 | 
				
			||||||
             <property name="toolTip">
 | 
					             <property name="toolTip">
 | 
				
			||||||
              <string><p>If checked, the template evaluator will stop when it
 | 
					              <string><p>If checked then the template will be run (tested) after every
 | 
				
			||||||
evaluates an expression on a double-clicked line number, opening a dialog showing
 | 
					keystroke. If unchecked then the template will be run when the "Go" button is pushed.</p></string>
 | 
				
			||||||
you the value as well as all the local variables</p></string>
 | 
					 | 
				
			||||||
             </property>
 | 
					             </property>
 | 
				
			||||||
            </widget>
 | 
					            </widget>
 | 
				
			||||||
           </item>
 | 
					           </item>
 | 
				
			||||||
@ -237,7 +236,7 @@ you the value as well as all the local variables</p></string>
 | 
				
			|||||||
              <set>Qt::ToolButtonTextBesideIcon</set>
 | 
					              <set>Qt::ToolButtonTextBesideIcon</set>
 | 
				
			||||||
             </property>
 | 
					             </property>
 | 
				
			||||||
             <property name="toolTip">
 | 
					             <property name="toolTip">
 | 
				
			||||||
              <string>If 'Enable breakpoints' is checked then click this button to run your template</string>
 | 
					              <string>If 'Run as you type' is not checked then click this button to run your template</string>
 | 
				
			||||||
             </property>
 | 
					             </property>
 | 
				
			||||||
            </widget>
 | 
					            </widget>
 | 
				
			||||||
           </item>
 | 
					           </item>
 | 
				
			||||||
@ -248,6 +247,18 @@ you the value as well as all the local variables</p></string>
 | 
				
			|||||||
             </property>
 | 
					             </property>
 | 
				
			||||||
            </widget>
 | 
					            </widget>
 | 
				
			||||||
           </item>
 | 
					           </item>
 | 
				
			||||||
 | 
					           <item>
 | 
				
			||||||
 | 
					            <widget class="QCheckBox" name="break_box">
 | 
				
			||||||
 | 
					             <property name="text">
 | 
				
			||||||
 | 
					              <string>Enable &breakpoints</string>
 | 
				
			||||||
 | 
					             </property>
 | 
				
			||||||
 | 
					             <property name="toolTip">
 | 
				
			||||||
 | 
					              <string><p>If checked, the template evaluator will stop when it
 | 
				
			||||||
 | 
					evaluates an expression on a double-clicked line number, opening a dialog showing
 | 
				
			||||||
 | 
					you the value as well as all the local variables</p></string>
 | 
				
			||||||
 | 
					             </property>
 | 
				
			||||||
 | 
					            </widget>
 | 
				
			||||||
 | 
					           </item>
 | 
				
			||||||
           <item>
 | 
					           <item>
 | 
				
			||||||
            <widget class="QLabel" name="breakpoint_line_box_label">
 | 
					            <widget class="QLabel" name="breakpoint_line_box_label">
 | 
				
			||||||
             <property name="text">
 | 
					             <property name="text">
 | 
				
			||||||
@ -378,17 +389,34 @@ you the value as well as all the local variables</p></string>
 | 
				
			|||||||
          </widget>
 | 
					          </widget>
 | 
				
			||||||
         </item>
 | 
					         </item>
 | 
				
			||||||
         <item row="7" column="0">
 | 
					         <item row="7" column="0">
 | 
				
			||||||
          <widget class="QLabel">
 | 
					          <layout class="QHBoxLayout">
 | 
				
			||||||
           <property name="text">
 | 
					           <item>
 | 
				
			||||||
            <string>Template value:</string>
 | 
					            <widget class="QLabel">
 | 
				
			||||||
           </property>
 | 
					             <property name="text">
 | 
				
			||||||
           <property name="buddy">
 | 
					              <string>Template value:</string>
 | 
				
			||||||
            <cstring>template_value</cstring>
 | 
					             </property>
 | 
				
			||||||
           </property>
 | 
					             <property name="buddy">
 | 
				
			||||||
           <property name="toolTip">
 | 
					              <cstring>template_value</cstring>
 | 
				
			||||||
            <string>The value of the template using the current book in the library view</string>
 | 
					             </property>
 | 
				
			||||||
           </property>
 | 
					             <property name="toolTip">
 | 
				
			||||||
          </widget>
 | 
					              <string>The value of the template using the currently selected book(s)
 | 
				
			||||||
 | 
					in the library view</string>
 | 
				
			||||||
 | 
					             </property>
 | 
				
			||||||
 | 
					            </widget>
 | 
				
			||||||
 | 
					           </item>
 | 
				
			||||||
 | 
					           <item>
 | 
				
			||||||
 | 
					            <widget class="QCheckBox" name="show_all_selected_books">
 | 
				
			||||||
 | 
					             <property name="text">
 | 
				
			||||||
 | 
					              <string>Show template result for all selected books</string>
 | 
				
			||||||
 | 
					             </property>
 | 
				
			||||||
 | 
					             <property name="toolTip">
 | 
				
			||||||
 | 
					              <string><p>If checked then the template will be evaluated for all selected
 | 
				
			||||||
 | 
					books. If not checked then the template will be evaluated for the first selected book. Unchecking this
 | 
				
			||||||
 | 
					box is useful if the template uses the selected books to generate a report.</p></string>
 | 
				
			||||||
 | 
					             </property>
 | 
				
			||||||
 | 
					            </widget>
 | 
				
			||||||
 | 
					           </item>
 | 
				
			||||||
 | 
					          </layout>
 | 
				
			||||||
         </item>
 | 
					         </item>
 | 
				
			||||||
         <item row="8" column="0" colspan="3">
 | 
					         <item row="8" column="0" colspan="3">
 | 
				
			||||||
          <widget class="QTableWidget" name="template_value">
 | 
					          <widget class="QTableWidget" name="template_value">
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,7 @@ class Node:
 | 
				
			|||||||
    NODE_SWITCH = 31
 | 
					    NODE_SWITCH = 31
 | 
				
			||||||
    NODE_SWITCH_IF = 32
 | 
					    NODE_SWITCH_IF = 32
 | 
				
			||||||
    NODE_LIST_COUNT_FIELD = 33
 | 
					    NODE_LIST_COUNT_FIELD = 33
 | 
				
			||||||
 | 
					    NODE_WITH = 34
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, line_number, name):
 | 
					    def __init__(self, line_number, name):
 | 
				
			||||||
        self.my_line_number = line_number
 | 
					        self.my_line_number = line_number
 | 
				
			||||||
@ -74,6 +75,14 @@ class Node:
 | 
				
			|||||||
        return self.my_line_number
 | 
					        return self.my_line_number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WithNode(Node):
 | 
				
			||||||
 | 
					    def __init__(self, line_number, book_id, block):
 | 
				
			||||||
 | 
					        Node.__init__(self, line_number, 'if ...')
 | 
				
			||||||
 | 
					        self.node_type = self.NODE_WITH
 | 
				
			||||||
 | 
					        self.book_id = book_id
 | 
				
			||||||
 | 
					        self.block = block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IfNode(Node):
 | 
					class IfNode(Node):
 | 
				
			||||||
    def __init__(self, line_number, condition, then_part, else_part):
 | 
					    def __init__(self, line_number, condition, then_part, else_part):
 | 
				
			||||||
        Node.__init__(self, line_number, 'if ...')
 | 
					        Node.__init__(self, line_number, 'if ...')
 | 
				
			||||||
@ -600,6 +609,20 @@ class _Parser:
 | 
				
			|||||||
    def local_call_expression(self, name, arguments):
 | 
					    def local_call_expression(self, name, arguments):
 | 
				
			||||||
        return LocalFunctionCallNode(self.line_number, name, arguments)
 | 
					        return LocalFunctionCallNode(self.line_number, name, arguments)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def with_expression(self):
 | 
				
			||||||
 | 
					        self.consume()
 | 
				
			||||||
 | 
					        line_number = self.line_number
 | 
				
			||||||
 | 
					        book_id = self.top_expr()
 | 
				
			||||||
 | 
					        if not self.token_op_is(':'):
 | 
				
			||||||
 | 
					            self.error(_("{0} statement: expected '{1}', "
 | 
				
			||||||
 | 
					                         "found '{2}'").format('with', ':', self.token_text()))
 | 
				
			||||||
 | 
					        self.consume()
 | 
				
			||||||
 | 
					        block = self.expression_list()
 | 
				
			||||||
 | 
					        if not self.token_is('htiw'):
 | 
				
			||||||
 | 
					            self.error(_("'{0}' statement: missing the closing '{1}'").format('def', 'fed'))
 | 
				
			||||||
 | 
					        self.consume()
 | 
				
			||||||
 | 
					        return WithNode(line_number, book_id, block)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def call_expression(self, name, arguments):
 | 
					    def call_expression(self, name, arguments):
 | 
				
			||||||
        compiled_func = self.funcs[name].cached_compiled_text
 | 
					        compiled_func = self.funcs[name].cached_compiled_text
 | 
				
			||||||
        if compiled_func is None:
 | 
					        if compiled_func is None:
 | 
				
			||||||
@ -692,6 +715,7 @@ class _Parser:
 | 
				
			|||||||
            'continue': (lambda self: self.consume(), lambda self: ContinueNode(self.line_number)),
 | 
					            'continue': (lambda self: self.consume(), lambda self: ContinueNode(self.line_number)),
 | 
				
			||||||
            'return':   (lambda self: self.consume(), lambda self: ReturnNode(self.line_number, self.top_expr())),
 | 
					            'return':   (lambda self: self.consume(), lambda self: ReturnNode(self.line_number, self.top_expr())),
 | 
				
			||||||
            'def':      (lambda self: None, define_function_expression),
 | 
					            'def':      (lambda self: None, define_function_expression),
 | 
				
			||||||
 | 
					            'with':     (lambda self: None, with_expression)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # {inlined_function_name: tuple(constraint on number of length, node builder) }
 | 
					    # {inlined_function_name: tuple(constraint on number of length, node builder) }
 | 
				
			||||||
@ -1017,6 +1041,27 @@ class _Interpreter:
 | 
				
			|||||||
            raise e
 | 
					            raise e
 | 
				
			||||||
        return val
 | 
					        return val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_node_with(self, prog):
 | 
				
			||||||
 | 
					        line_number = prog.line_number
 | 
				
			||||||
 | 
					        parent_book = self.parent_book
 | 
				
			||||||
 | 
					        v = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            book_id = int(self.expr(prog.book_id))
 | 
				
			||||||
 | 
					            if self.break_reporter:
 | 
				
			||||||
 | 
					                self.break_reporter("'with': book id ", str(book_id), line_number)
 | 
				
			||||||
 | 
					            self.parent_book = self.parent.book = get_database(
 | 
				
			||||||
 | 
					                    self.parent_book, 'with statement').new_api.get_proxy_metadata(book_id)
 | 
				
			||||||
 | 
					            v = self.expression_list(prog.block)
 | 
				
			||||||
 | 
					            if self.break_reporter:
 | 
				
			||||||
 | 
					                self.break_reporter("'with': block value", v, line_number)
 | 
				
			||||||
 | 
					            return v
 | 
				
			||||||
 | 
					        except (StopException, ValueError, ReturnExecuted) as e:
 | 
				
			||||||
 | 
					            raise e
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            self.error(_("Unhandled exception '{0}'").format(e), line_number)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.parent_book = self.parent.book = parent_book
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def do_node_if(self, prog):
 | 
					    def do_node_if(self, prog):
 | 
				
			||||||
        line_number = prog.line_number
 | 
					        line_number = prog.line_number
 | 
				
			||||||
        test_part = self.expr(prog.condition)
 | 
					        test_part = self.expr(prog.condition)
 | 
				
			||||||
@ -1608,7 +1653,8 @@ class _Interpreter:
 | 
				
			|||||||
        Node.NODE_LOCAL_FUNCTION_DEFINE: do_node_local_function_define,
 | 
					        Node.NODE_LOCAL_FUNCTION_DEFINE: do_node_local_function_define,
 | 
				
			||||||
        Node.NODE_LOCAL_FUNCTION_CALL:   do_node_local_function_call,
 | 
					        Node.NODE_LOCAL_FUNCTION_CALL:   do_node_local_function_call,
 | 
				
			||||||
        Node.NODE_LIST_COUNT_FIELD:      do_node_list_count_field,
 | 
					        Node.NODE_LIST_COUNT_FIELD:      do_node_list_count_field,
 | 
				
			||||||
        }
 | 
					        Node.NODE_WITH:                  do_node_with,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def expr(self, prog):
 | 
					    def expr(self, prog):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@ -1707,6 +1753,7 @@ class TemplateFormatter(string.Formatter):
 | 
				
			|||||||
            (r'(def|fed|continue)\b',    lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
					            (r'(def|fed|continue)\b',    lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
				
			||||||
            (r'(return|inlist|break)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
					            (r'(return|inlist|break)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
				
			||||||
            (r'(inlist_field)\b',        lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
					            (r'(inlist_field)\b',        lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
				
			||||||
 | 
					            (r'(with|htiw)\b',           lambda x,t: (_Parser.LEX_KEYWORD, t)),
 | 
				
			||||||
            (r'(\|\||&&|!|{|})',         lambda x,t: (_Parser.LEX_OP, t)),
 | 
					            (r'(\|\||&&|!|{|})',         lambda x,t: (_Parser.LEX_OP, t)),
 | 
				
			||||||
            (r'[(),=;:\+\-*/&]',         lambda x,t: (_Parser.LEX_OP, t)),
 | 
					            (r'[(),=;:\+\-*/&]',         lambda x,t: (_Parser.LEX_OP, t)),
 | 
				
			||||||
            (r'-?[\d\.]+',               lambda x,t: (_Parser.LEX_CONST, t)),
 | 
					            (r'-?[\d\.]+',               lambda x,t: (_Parser.LEX_CONST, t)),
 | 
				
			||||||
 | 
				
			|||||||
@ -55,6 +55,7 @@ CASE_CHANGES = _('Case changes')
 | 
				
			|||||||
DATE_FUNCTIONS = _('Date functions')
 | 
					DATE_FUNCTIONS = _('Date functions')
 | 
				
			||||||
DB_FUNCS = _('Database functions')
 | 
					DB_FUNCS = _('Database functions')
 | 
				
			||||||
URL_FUNCTIONS = _('URL functions')
 | 
					URL_FUNCTIONS = _('URL functions')
 | 
				
			||||||
 | 
					GUI_FUNCTIONS = __('GUI functions')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Class and method to save an untranslated copy of translated strings
 | 
					# Class and method to save an untranslated copy of translated strings
 | 
				
			||||||
@ -1249,7 +1250,7 @@ string.
 | 
				
			|||||||
class BuiltinApproximateFormats(BuiltinFormatterFunction):
 | 
					class BuiltinApproximateFormats(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'approximate_formats'
 | 
					    name = 'approximate_formats'
 | 
				
			||||||
    arg_count = 0
 | 
					    arg_count = 0
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
``approximate_formats()`` -- return a comma-separated list of formats associated
 | 
					``approximate_formats()`` -- return a comma-separated list of formats associated
 | 
				
			||||||
@ -1278,7 +1279,7 @@ column's value in your save/send templates.
 | 
				
			|||||||
class BuiltinFormatsModtimes(BuiltinFormatterFunction):
 | 
					class BuiltinFormatsModtimes(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'formats_modtimes'
 | 
					    name = 'formats_modtimes'
 | 
				
			||||||
    arg_count = 1
 | 
					    arg_count = 1
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
``formats_modtimes(date_format_string)`` -- return a comma-separated list of
 | 
					``formats_modtimes(date_format_string)`` -- return a comma-separated list of
 | 
				
			||||||
@ -1302,7 +1303,7 @@ that format names are always uppercase, as in EPUB.
 | 
				
			|||||||
class BuiltinFormatsSizes(BuiltinFormatterFunction):
 | 
					class BuiltinFormatsSizes(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'formats_sizes'
 | 
					    name = 'formats_sizes'
 | 
				
			||||||
    arg_count = 0
 | 
					    arg_count = 0
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1323,7 +1324,7 @@ format names are always uppercase, as in EPUB.
 | 
				
			|||||||
class BuiltinFormatsPaths(BuiltinFormatterFunction):
 | 
					class BuiltinFormatsPaths(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'formats_paths'
 | 
					    name = 'formats_paths'
 | 
				
			||||||
    arg_count = -1
 | 
					    arg_count = -1
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
``formats_paths([separator])`` -- return a ``separator``-separated list of
 | 
					``formats_paths([separator])`` -- return a ``separator``-separated list of
 | 
				
			||||||
@ -1345,7 +1346,7 @@ format names are always uppercase, as in EPUB.
 | 
				
			|||||||
class BuiltinFormatsPathSegments(BuiltinFormatterFunction):
 | 
					class BuiltinFormatsPathSegments(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'formats_path_segments'
 | 
					    name = 'formats_path_segments'
 | 
				
			||||||
    arg_count = 5
 | 
					    arg_count = 5
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
``formats_path_segments(with_author, with_title, with_format, with_ext, sep)``
 | 
					``formats_path_segments(with_author, with_title, with_format, with_ext, sep)``
 | 
				
			||||||
@ -1775,7 +1776,7 @@ template, and use that column\'s value in your save/send templates.
 | 
				
			|||||||
class BuiltinAnnotationCount(BuiltinFormatterFunction):
 | 
					class BuiltinAnnotationCount(BuiltinFormatterFunction):
 | 
				
			||||||
    name = 'annotation_count'
 | 
					    name = 'annotation_count'
 | 
				
			||||||
    arg_count = 0
 | 
					    arg_count = 0
 | 
				
			||||||
    category = GET_FROM_METADATA
 | 
					    category = DB_FUNCS
 | 
				
			||||||
    def __doc__getter__(self): return translate_ffml(
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
r'''
 | 
					r'''
 | 
				
			||||||
``annotation_count()`` -- return the total number of annotations of all types
 | 
					``annotation_count()`` -- return the total number of annotations of all types
 | 
				
			||||||
@ -3649,6 +3650,116 @@ This can be useful to truncate a value.
 | 
				
			|||||||
        return pat.sub(repl, template)
 | 
					        return pat.sub(repl, template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuiltinSelectedBooks(BuiltinFormatterFunction):
 | 
				
			||||||
 | 
					    name = 'selected_books'
 | 
				
			||||||
 | 
					    arg_count = 0
 | 
				
			||||||
 | 
					    category = GUI_FUNCTIONS
 | 
				
			||||||
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
 | 
					r'''
 | 
				
			||||||
 | 
					``selected_books([sorted_by, ascending])`` -- returns a list of book ids in
 | 
				
			||||||
 | 
					selection order for the currently selected books.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This function can be used only in the GUI.
 | 
				
			||||||
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def evaluate(self, formatter, kwargs, mi, locals, *args):
 | 
				
			||||||
 | 
					        from calibre.gui2.ui import get_gui
 | 
				
			||||||
 | 
					        g = get_gui()
 | 
				
			||||||
 | 
					        book_ids = g.current_view().get_selected_ids()
 | 
				
			||||||
 | 
					        return ', '.join([str(book_id) for book_id in book_ids])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuiltinSortBookIds(BuiltinFormatterFunction):
 | 
				
			||||||
 | 
					    name = 'sort_book_ids'
 | 
				
			||||||
 | 
					    arg_count = -1
 | 
				
			||||||
 | 
					    category = GUI_FUNCTIONS
 | 
				
			||||||
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
 | 
					r'''
 | 
				
			||||||
 | 
					``sort_book_ids(book_ids, sorted_by, ascending [, sorted_by, ascending]*)`` --
 | 
				
			||||||
 | 
					returns the list of book ids sorted by the column specified by the lookup name
 | 
				
			||||||
 | 
					in ``sorted_by`` in the order specified by ``ascending``. If ``ascending`` is
 | 
				
			||||||
 | 
					``'1'`` then the books are sorted by the value in the 'sorted_by' column in
 | 
				
			||||||
 | 
					ascending order, otherwise in descending order. You can have multiple pairs of
 | 
				
			||||||
 | 
					``sorted_by, ascending``. The first pair specifies the major order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This function can be used only in the GUI.
 | 
				
			||||||
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def evaluate(self, formatter, kwargs, mi, locals, book_ids, *args):
 | 
				
			||||||
 | 
					        from calibre.gui2.ui import get_gui
 | 
				
			||||||
 | 
					        g = get_gui()
 | 
				
			||||||
 | 
					        bids = [int(b.strip()) for b in book_ids.split(',')]
 | 
				
			||||||
 | 
					        if len(args) < 2:
 | 
				
			||||||
 | 
					            raise ValueError(_('The sort_book_ids function requires at least 3 arguments'))
 | 
				
			||||||
 | 
					        if len(args) % 2 != 0:
 | 
				
			||||||
 | 
					            raise ValueError(_('The id and direction arguments must be in pairs'))
 | 
				
			||||||
 | 
					        sort_spec = []
 | 
				
			||||||
 | 
					        for i in range(0, len(args), 2):
 | 
				
			||||||
 | 
					            sort_by = args[i]
 | 
				
			||||||
 | 
					            asc = True if args[i+1] == '1' else False
 | 
				
			||||||
 | 
					            sort_spec.append((sort_by, asc))
 | 
				
			||||||
 | 
					        bids = g.current_db.new_api.multisort(sort_spec, bids)
 | 
				
			||||||
 | 
					        return ', '.join([str(b) for b in bids])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuiltinSelectedColumn(BuiltinFormatterFunction):
 | 
				
			||||||
 | 
					    name = 'selected_column'
 | 
				
			||||||
 | 
					    arg_count = 0
 | 
				
			||||||
 | 
					    category = GUI_FUNCTIONS
 | 
				
			||||||
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
 | 
					r'''
 | 
				
			||||||
 | 
					``selected_column()`` -- returns the lookup name of the column containing the currently
 | 
				
			||||||
 | 
					selected cell. It returns ``''`` if no cell is selected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This function can be used only in the GUI.
 | 
				
			||||||
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def evaluate(self, formatter, kwargs, mi, locals):
 | 
				
			||||||
 | 
					        from calibre.gui2.ui import get_gui
 | 
				
			||||||
 | 
					        v = get_gui().current_view()
 | 
				
			||||||
 | 
					        idx = v.currentIndex()
 | 
				
			||||||
 | 
					        if idx.isValid():
 | 
				
			||||||
 | 
					            key = v.column_map[idx.column()]
 | 
				
			||||||
 | 
					            return key
 | 
				
			||||||
 | 
					        return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuiltinShowDialog(BuiltinFormatterFunction):
 | 
				
			||||||
 | 
					    name = 'show_dialog'
 | 
				
			||||||
 | 
					    arg_count = 1
 | 
				
			||||||
 | 
					    category = GUI_FUNCTIONS
 | 
				
			||||||
 | 
					    def __doc__getter__(self): return translate_ffml(
 | 
				
			||||||
 | 
					r'''
 | 
				
			||||||
 | 
					``show_dialog(html_or_text)`` -- show a dialog containing the html or text. The
 | 
				
			||||||
 | 
					function returns ``'1'`` if the user presses OK, ``''`` if Cancel.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This function can be used only in the GUI.
 | 
				
			||||||
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def evaluate(self, formatter, kwargs, mi, locals, html):
 | 
				
			||||||
 | 
					        from calibre.gui2.widgets2 import Dialog, HTMLDisplay
 | 
				
			||||||
 | 
					        from qt.core import QDialog, QVBoxLayout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class HTMLDialog(Dialog):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def __init__(self, title, prefs):
 | 
				
			||||||
 | 
					                super().__init__(title, 'formatter_html_dialog', prefs=prefs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def setup_ui(self):
 | 
				
			||||||
 | 
					                l = QVBoxLayout(self)
 | 
				
			||||||
 | 
					                d = self.display = HTMLDisplay()
 | 
				
			||||||
 | 
					                l.addWidget(d)
 | 
				
			||||||
 | 
					                l.addWidget(self.bb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def set_html(self, tt_text):
 | 
				
			||||||
 | 
					                self.display.setHtml(tt_text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = get_database(mi, 'show_dialog')
 | 
				
			||||||
 | 
					        d = HTMLDialog(_('Template output'), db.new_api.backend.prefs)
 | 
				
			||||||
 | 
					        d.set_html(html)
 | 
				
			||||||
 | 
					        return '1' if d.exec() == QDialog.DialogCode.Accepted else ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_formatter_builtins = [
 | 
					_formatter_builtins = [
 | 
				
			||||||
    BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
 | 
					    BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
 | 
				
			||||||
    BuiltinAssign(),
 | 
					    BuiltinAssign(),
 | 
				
			||||||
@ -3677,8 +3788,10 @@ _formatter_builtins = [
 | 
				
			|||||||
    BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
 | 
					    BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
 | 
				
			||||||
    BuiltinOr(), BuiltinPrint(), BuiltinQueryString(), BuiltinRatingToStars(),
 | 
					    BuiltinOr(), BuiltinPrint(), BuiltinQueryString(), BuiltinRatingToStars(),
 | 
				
			||||||
    BuiltinRange(), BuiltinRawField(), BuiltinRawList(),
 | 
					    BuiltinRange(), BuiltinRawField(), BuiltinRawList(),
 | 
				
			||||||
    BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(),
 | 
					    BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(),
 | 
				
			||||||
    BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
 | 
					    BuiltinSelectedBooks(), BuiltinSelectedColumn(), BuiltinSeriesSort(),
 | 
				
			||||||
 | 
					    BuiltinSetGlobals(), BuiltinShorten(), BuiltinShowDialog(), BuiltinSortBookIds(),
 | 
				
			||||||
 | 
					    BuiltinStrcat(), BuiltinStrcatMax(),
 | 
				
			||||||
    BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
 | 
					    BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
 | 
				
			||||||
    BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(),
 | 
					    BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(),
 | 
				
			||||||
    BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwitchIf(),
 | 
					    BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwitchIf(),
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user