From 728f82cf44238dd5707ff1aecde5dc6b5f2708bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Nov 2014 13:14:31 +0530 Subject: [PATCH] Edit Book: Search & Replace function mode: Add a new function annotation (replace.file_order) to control what order multiple files are processed in, when doing Replace All. See the User Manual for details. --- manual/function_mode.rst | 43 +++++++++++++++---- .../gui2/tweak_book/function_replace.py | 1 + src/calibre/gui2/tweak_book/search.py | 12 +++++- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/manual/function_mode.rst b/manual/function_mode.rst index 56d8214981..52f346a5c5 100644 --- a/manual/function_mode.rst +++ b/manual/function_mode.rst @@ -129,15 +129,24 @@ they look like :code:`

1. Some text

`. section_number = '%d. ' % number return match.group(1) + section_number + match.group(2) + # Ensure that when running over multiple files, the files are processed + # in the order in which they appear in the book + replace.file_order = 'spine' + Use it with the find expression:: (?s)(]*>)(.+?) -Place the cursor at the top of the file and click :guilabel:`Replace all`. This -function uses another of the useful extra arguments to ``replace()``: the +Place the cursor at the top of the file and click :guilabel:`Replace all`. + +This function uses another of the useful extra arguments to ``replace()``: the ``number`` argument. When doing a :guilabel:`Replace All` number is automatically incremented for every successive match. +Another new feature is the use of ``replace.file_order`` -- setting that to +``'spine'`` means that if this search is run on multiple HTML files, the files +are processed in the order in which they appear in the book. + Auto create a Table of Contents ------------------------------------- @@ -159,16 +168,12 @@ Contents based on these headings. Create the custom function below: # All matches found, output the resulting Table of Contents. # The argument metadata is the metadata of the book being edited if 'toc' in data: - book = current_container() toc = data['toc'] - # Re-arrange the entries in the spine order of the book - spine_order = {name:i for i, (name, is_linear) in enumerate(book.spine_names)} - toc.sort(key=lambda x: spine_order.get(x[0])) root = TOC() for (file_name, tag_name, anchor, text) in toc: parent = root.children[-1] if tag_name == 'h2' and root.children else root parent.add(text, file_name, anchor) - toc = toc_to_html(root, book, 'toc.html', 'Table of Contents for ' + metadata.title, metadata.language) + toc = toc_to_html(root, current_container(), 'toc.html', 'Table of Contents for ' + metadata.title, metadata.language) print (xml2str(toc)) else: print ('No headings to build ToC from found') @@ -185,6 +190,9 @@ Contents based on these headings. Create the custom function below: # Ensure that we are called once after the last match is found so we can # output the ToC replace.call_after_last_match = True + # Ensure that when running over multiple files, this function is called, + # the files are processed in the order in which they appear in the book + replace.file_order = 'spine' And use it with the find expression:: @@ -325,12 +333,31 @@ function from python. The output of print will be displayed in a popup window after the Find/replace has completed. You saw an example of using ``print()`` to output an entire table of contents above. +Choose file order when running on multiple HTML files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When you run a :guilabel:`Replace All` on multiple HTML files, the order in +which the files are processes depends on what files you have open for editing. +You can force the search to process files in the order in which the appear by +setting the ``file_order`` attribute on your function, like this: + +.. code-block:: python + + def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs): + ... + + replace.file_order = 'spine' + +``file_order`` accepts two values, ``spine`` and ``spine-reverse`` which cause +the search to process multiple files in the order they appear in the book, +either forwards or backwards, respectively. + Having your function called an extra time after the last match is found ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sometimes, as in the auto generate table of contents example above, it is useful to have your function called an extra time after the last match is -found. You can do this by setting the call_after_last_match attribute on your +found. You can do this by setting the ``call_after_last_match`` attribute on your function, like this: .. code-block:: python diff --git a/src/calibre/gui2/tweak_book/function_replace.py b/src/calibre/gui2/tweak_book/function_replace.py index 984bacbd55..f03812dae1 100644 --- a/src/calibre/gui2/tweak_book/function_replace.py +++ b/src/calibre/gui2/tweak_book/function_replace.py @@ -56,6 +56,7 @@ class Function(object): self.mod = None if not callable(self.func): raise ValueError('%r is not a function' % self.func) + self.file_order = getattr(self.func, 'file_order', None) def init_env(self, name=''): from calibre.gui2.tweak_book.boss import get_boss diff --git a/src/calibre/gui2/tweak_book/search.py b/src/calibre/gui2/tweak_book/search.py index e97c3765ae..f366b7a45a 100644 --- a/src/calibre/gui2/tweak_book/search.py +++ b/src/calibre/gui2/tweak_book/search.py @@ -1193,6 +1193,11 @@ def show_function_debug_output(func): from calibre.gui2.tweak_book.boss import get_boss get_boss().gui.sr_debug_output.show_log(func.name, val) +def reorder_files(names, order): + reverse = order in {'spine-reverse', 'reverse-spine'} + spine_order = {name:i for i, (name, is_linear) in enumerate(current_container().spine_names)} + return sorted(frozenset(names), key=spine_order.get, reverse=reverse) + def run_search( searches, action, current_editor, current_editor_name, searchable_names, gui_parent, show_editor, edit_file, show_current_diff, add_savepoint, rewind_savepoint, set_modified): @@ -1292,7 +1297,7 @@ def run_search( lfiles = files or {current_editor_name:editor.syntax} updates = set() raw_data = {} - for n, syntax in lfiles.iteritems(): + for n in lfiles: if n in editors: raw = editors[n].get_raw_data() else: @@ -1301,9 +1306,12 @@ def run_search( for p, repl in searches: repl_is_func = isinstance(repl, Function) + file_iterator = lfiles if repl_is_func: repl.init_env() - for n, syntax in lfiles.iteritems(): + if repl.file_order is not None and len(lfiles) > 1: + file_iterator = reorder_files(file_iterator, repl.file_order) + for n in file_iterator: raw = raw_data[n] if replace: if repl_is_func: