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.

This commit is contained in:
Kovid Goyal 2014-11-23 13:14:31 +05:30
parent f89b974b14
commit 728f82cf44
3 changed files with 46 additions and 10 deletions

View File

@ -129,15 +129,24 @@ they look like :code:`<h2>1. Some text</h2>`.
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)(<h2[^<>]*>)(.+?</h2>)
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

View File

@ -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

View File

@ -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: