Quote:
Originally Posted by thiago.eec
Thank you very much. This custom action gave me full control of the editor!
Now, one last question:
I've always wanted to automate merging splitted files (filename_split0001.xhtml, filename_split0002.xhtml, etc). With the custom action you provided, I can access the 'Merge files' action, but I didn't find a way to select the files based on a regular expression, just like when using 'Search and replace' and the filename filter.
Is it possible?
|
Not without an action to convert scopes to selection. That one if fairly straight forward:
Code:
from qt.core import (QWidget, QVBoxLayout, QGroupBox)
from calibre_plugins.editor_chains.actions.base import EditorAction
from calibre_plugins.editor_chains.scope import scope_names, ScopeWidget, validate_scope, scope_is_headless
class ConfigWidget(QWidget):
def __init__(self, plugin_action):
QWidget.__init__(self)
self.plugin_action = plugin_action
self._init_controls()
def _init_controls(self):
l = self.l = QVBoxLayout()
self.setLayout(l)
scope_groupbox = QGroupBox(_('Files to select'))
l.addWidget(scope_groupbox)
scope_l = QVBoxLayout()
scope_groupbox.setLayout(scope_l)
self.where_box = ScopeWidget(self, self.plugin_action, orientation='vertical',
headless=self.plugin_action.gui is None,
hide=['selected-text','open','current'])
scope_l.addWidget(self.where_box)
l.addStretch(1)
self.setMinimumSize(400,200)
def load_settings(self, settings):
if settings:
self.where_box.where = settings['where']
def save_settings(self):
settings = {}
settings['where'] = self.where_box.where
return settings
class SelectFiles(EditorAction):
name = 'Select Files'
headless = True
def run(self, chain, settings, *args, **kwargs):
names = scope_names(chain, settings['where'])
chain.gui.file_list.select_names(names)
def validate(self, settings):
scope_ok = validate_scope(settings['where'])
if scope_ok is not True:
return scope_ok
return True
def config_widget(self):
return ConfigWidget
def is_headless(self, settings):
return scope_is_headless(settings['where'])
The action above will select files based on filters. Note however, that merge in an interactive step which will require you to select the master file into which all other files will be merged.
Calibre uses a function called merge() to merge files. You can build an action around this function:
Code:
from qt.core import (QWidget, QVBoxLayout, QGroupBox)
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
from calibre.ebooks.oeb.polish.split import merge
from calibre.gui2.tweak_book import editors
from calibre_plugins.editor_chains.actions.base import EditorAction
from calibre_plugins.editor_chains.scope import scope_names, ScopeWidget, validate_scope, scope_is_headless
def spine_index(container, name):
idx = 0
for spine_name, is_linear in container.spine_names:
idx +=1
if name == spine_name:
return idx
class ConfigWidget(QWidget):
def __init__(self, plugin_action):
QWidget.__init__(self)
self.plugin_action = plugin_action
self._init_controls()
def _init_controls(self):
l = self.l = QVBoxLayout()
self.setLayout(l)
scope_groupbox = QGroupBox(_('Files to merge'))
l.addWidget(scope_groupbox)
scope_l = QVBoxLayout()
scope_groupbox.setLayout(scope_l)
self.where_box = ScopeWidget(self, self.plugin_action, orientation='vertical',
headless=self.plugin_action.gui is None,
hide=['selected-text','open','current'])
scope_l.addWidget(self.where_box)
l.addStretch(1)
self.setMinimumSize(400,200)
def load_settings(self, settings):
if settings:
self.where_box.where = settings['where']
def save_settings(self):
settings = {}
settings['where'] = self.where_box.where
return settings
class MergeFiles(EditorAction):
name = 'Merge Files'
headless = True
def run(self, chain, settings, *args, **kwargs):
names = scope_names(chain, settings['where'])
print(f'DEBUG: Merging Files\n names: {names}')
if len(names) < 2:
print('Error: You need to select at least 2 files to merge')
return
container = chain.current_container
spine_names = [name for name, is_linear in container.spine_names]
if names[0] in set(container.manifest_items_of_type(OEB_DOCS)):
category = 'text'
elif names[0] in set(container.manifest_items_of_type(OEB_STYLES)):
category = 'styles'
else:
print('Error: Can only merge files of categories: text or styles')
return
if category == 'text':
if not set(names).issubset(set(container.manifest_items_of_type(OEB_DOCS))):
print('Error: some files are your trying to merge are not of category text')
return
if not set(names).issubset(set(spine_names)):
print('Error: Some files you are trying to merge not in spine')
return
names = sorted(names, key=lambda name: spine_index(container, name))
else:
if not set(names).issubset(set(container.manifest_items_of_type(OEB_STYLES))):
print('Error: some files are your trying to merge are not of category styles')
return
names = sorted(names)
master = names[0]
print(f'DEBUG: Merging Files\n master: {master}')
merge(container, category, names, master)
if master in editors:
chain.boss.show_editor(master)
def validate(self, settings):
scope_ok = validate_scope(settings['where'])
if scope_ok is not True:
return scope_ok
return True
def config_widget(self):
return ConfigWidget
def is_headless(self, settings):
return scope_is_headless(settings['where'])
Notes:- The action above will merge text files according to their spine index. The master file into which others will be merged is the one with the lowest spine index. Style files are sorted alphabetically.
- This action is provided without any guarantee that it will work properly. I have not tested it much. You must test vigorously to ensure it merges files in correct order before accepting the results it provides