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: