diff --git a/src/calibre/build_forms.py b/src/calibre/build_forms.py
index 222aff76bd..3912198da9 100644
--- a/src/calibre/build_forms.py
+++ b/src/calibre/build_forms.py
@@ -87,7 +87,7 @@ def build_forms(srcdir, info=None, summary=False, check_for_migration=False, che
open(compiled_form, 'wb').write(dat)
num += 1
if num:
- info('Compiled %d forms' % num)
+ info(f'Compiled {num} forms')
if check_icons:
resource_dir = os.path.join(os.path.dirname(srcdir), 'resources')
ensure_icons_built(resource_dir, force_compile, info)
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index 9e267382cc..752097364f 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -808,7 +808,7 @@ def initialize_plugins(perf=False):
sys.stdout, sys.stderr = ostdout, ostderr
if perf:
for x in sorted(times, key=lambda x: times[x]):
- print('%50s: %.3f'%(x, times[x]))
+ print(f'{x:50}: {times[x]:.3f}')
_initialized_plugins.sort(key=lambda x: x.priority, reverse=True)
reread_filetype_plugins()
reread_metadata_plugins()
diff --git a/src/calibre/customize/zipplugin.py b/src/calibre/customize/zipplugin.py
index bd473673d8..fcf52013b5 100644
--- a/src/calibre/customize/zipplugin.py
+++ b/src/calibre/customize/zipplugin.py
@@ -350,7 +350,7 @@ class CalibrePluginFinder:
c = 0
while True:
c += 1
- plugin_name = 'dummy%d'%c
+ plugin_name = f'dummy{c}'
if plugin_name not in self.loaded_plugins:
break
else:
diff --git a/src/calibre/db/__init__.py b/src/calibre/db/__init__.py
index e5ac6dd5b2..6e20d86b98 100644
--- a/src/calibre/db/__init__.py
+++ b/src/calibre/db/__init__.py
@@ -93,7 +93,7 @@ def get_data_as_dict(self, prefix=None, authors_as_string=False, ids=None, conve
'languages'}.union(set(fdata))
for x, data in iteritems(fdata):
if data['datatype'] == 'series':
- FIELDS.add('%d_index'%x)
+ FIELDS.add(f'{x}_index')
data = []
for record in self.data:
if record is None:
diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py
index 9001d2a050..8cbceb276f 100644
--- a/src/calibre/db/backend.py
+++ b/src/calibre/db/backend.py
@@ -614,11 +614,11 @@ class DB:
from calibre.library.coloring import migrate_old_rule
old_rules = []
for i in range(1, 6):
- col = self.prefs.get('column_color_name_%d' % i, None)
- templ = self.prefs.get('column_color_template_%d' % i, None)
+ col = self.prefs.get(f'column_color_name_{i}', None)
+ templ = self.prefs.get(f'column_color_template_{i}', None)
if col and templ:
try:
- del self.prefs['column_color_name_%d' % i]
+ del self.prefs[f'column_color_name_{i}']
rules = migrate_old_rule(self.field_metadata, templ)
for templ in rules:
old_rules.append((col, templ))
@@ -1410,7 +1410,7 @@ class DB:
with closing(Connection(tmpdb)) as conn:
shell = Shell(db=conn, encoding='utf-8')
shell.process_command('.read ' + fname.replace(os.sep, '/'))
- conn.execute('PRAGMA user_version=%d;'%uv)
+ conn.execute(f'PRAGMA user_version={uv};')
self.close(unload_formatter_functions=False)
try:
@@ -1495,7 +1495,7 @@ class DB:
# windows).
l = (self.PATH_LIMIT - (extlen // 2) - 2) if iswindows else ((self.PATH_LIMIT - extlen - 2) // 2)
if l < 5:
- raise ValueError('Extension length too long: %d' % extlen)
+ raise ValueError(f'Extension length too long: {extlen}')
author = ascii_filename(author)[:l]
title = ascii_filename(title.lstrip())[:l].rstrip()
if not title:
@@ -1510,7 +1510,7 @@ class DB:
# Database layer API {{{
def custom_table_names(self, num):
- return 'custom_column_%d'%num, 'books_custom_column_%d_link'%num
+ return f'custom_column_{num}', f'books_custom_column_{num}_link'
@property
def custom_tables(self):
@@ -1628,7 +1628,7 @@ class DB:
def format_hash(self, book_id, fmt, fname, path):
path = self.format_abspath(book_id, fmt, fname, path)
if path is None:
- raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt))
+ raise NoSuchFormat(f'Record {book_id} has no fmt: {fmt}')
sha = hashlib.sha256()
with open(path, 'rb') as f:
while True:
diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index 285ad41c13..8bd77968b3 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -992,7 +992,7 @@ class Cache:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except:
- raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt))
+ raise NoSuchFormat(f'Record {book_id} has no fmt: {fmt}')
return self.backend.format_hash(book_id, fmt, name, path)
@api
@@ -1222,7 +1222,7 @@ class Cache:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except (KeyError, AttributeError):
- raise NoSuchFormat('Record %d has no %s file'%(book_id, fmt))
+ raise NoSuchFormat(f'Record {book_id} has no {fmt} file')
return self.backend.copy_format_to(book_id, fmt, name, path, dest,
use_hardlink=use_hardlink, report_file_size=report_file_size)
@@ -2374,7 +2374,7 @@ class Cache:
removed. '''
missing = frozenset(val_map) - self._all_book_ids()
if missing:
- raise ValueError('add_custom_book_data: no such book_ids: %d'%missing)
+ raise ValueError(f'add_custom_book_data: no such book_ids: {missing}')
self.backend.add_custom_data(name, val_map, delete_first)
@read_api
diff --git a/src/calibre/db/cli/cmd_custom_columns.py b/src/calibre/db/cli/cmd_custom_columns.py
index c9487be434..1a041c9134 100644
--- a/src/calibre/db/cli/cmd_custom_columns.py
+++ b/src/calibre/db/cli/cmd_custom_columns.py
@@ -43,5 +43,5 @@ def main(opts, args, dbctx):
prints(pformat(data))
print('\n')
else:
- prints(col, '(%d)'%data['num'])
+ prints(col, f"({data['num']})")
return 0
diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py
index 5cbc738008..bca509df6b 100644
--- a/src/calibre/db/legacy.py
+++ b/src/calibre/db/legacy.py
@@ -654,7 +654,7 @@ class LibraryDatabase:
book_id = index if index_is_id else self.id(index)
ans = self.new_api.format_abspath(book_id, fmt)
if ans is None:
- raise NoSuchFormat('Record %d has no format: %s'%(book_id, fmt))
+ raise NoSuchFormat(f'Record {book_id} has no format: {fmt}')
return ans
def format_files(self, index, index_is_id=False):
diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py
index cd56aabe93..e73f01a57e 100644
--- a/src/calibre/db/schema_upgrades.py
+++ b/src/calibre/db/schema_upgrades.py
@@ -23,13 +23,13 @@ class SchemaUpgrade:
try:
while True:
uv = next(self.db.execute('pragma user_version'))[0]
- meth = getattr(self, 'upgrade_version_%d'%uv, None)
+ meth = getattr(self, f'upgrade_version_{uv}', None)
if meth is None:
break
else:
- prints('Upgrading database to version %d...'%(uv+1))
+ prints(f'Upgrading database to version {uv + 1}...')
meth()
- self.db.execute('pragma user_version=%d'%(uv+1))
+ self.db.execute(f'pragma user_version={uv + 1}')
except:
self.db.execute('ROLLBACK')
raise
diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py
index 86e1178ec8..fa7db6c358 100644
--- a/src/calibre/db/tests/filesystem.py
+++ b/src/calibre/db/tests/filesystem.py
@@ -279,7 +279,7 @@ class FilesystemTest(BaseTest):
self.assertFalse(importer.corrupted_files)
self.assertEqual(cache.all_book_ids(), ic.all_book_ids())
for book_id in cache.all_book_ids():
- self.assertEqual(cache.cover(book_id), ic.cover(book_id), 'Covers not identical for book: %d' % book_id)
+ self.assertEqual(cache.cover(book_id), ic.cover(book_id), f'Covers not identical for book: {book_id}')
for fmt in cache.formats(book_id):
self.assertEqual(cache.format(book_id, fmt), ic.format(book_id, fmt))
self.assertEqual(cache.format_metadata(book_id, fmt)['mtime'], cache.format_metadata(book_id, fmt)['mtime'])
diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py
index f22f1dcc61..d72db808c0 100644
--- a/src/calibre/db/tests/legacy.py
+++ b/src/calibre/db/tests/legacy.py
@@ -544,7 +544,7 @@ class LegacyTest(BaseTest):
n = now()
ndb = self.init_legacy(self.cloned_library)
amap = ndb.new_api.get_id_map('authors')
- sorts = [(aid, 's%d' % aid) for aid in amap]
+ sorts = [(aid, f's{aid}') for aid in amap]
db = self.init_old(self.cloned_library)
run_funcs(self, db, ndb, (
('+format_metadata', 1, 'FMT1', itemgetter('size')),
diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py
index 0f1226ce3d..42783b680c 100644
--- a/src/calibre/db/tests/reading.py
+++ b/src/calibre/db/tests/reading.py
@@ -126,8 +126,7 @@ class ReadingTest(BaseTest):
if isinstance(val, tuple) and 'authors' not in field and 'languages' not in field:
val, expected_val = set(val), set(expected_val)
self.assertEqual(expected_val, val,
- 'Book id: %d Field: %s failed: %r != %r'%(
- book_id, field, expected_val, val))
+ f'Book id: {book_id} Field: {field} failed: {expected_val!r} != {val!r}')
# }}}
def test_sorting(self): # {{{
@@ -206,7 +205,7 @@ class ReadingTest(BaseTest):
('title', True)]), 'Subsort failed')
from calibre.ebooks.metadata.book.base import Metadata
for i in range(7):
- cache.create_book_entry(Metadata('title%d' % i), apply_import_tags=False)
+ cache.create_book_entry(Metadata(f'title{i}'), apply_import_tags=False)
cache.create_custom_column('one', 'CC1', 'int', False)
cache.create_custom_column('two', 'CC2', 'int', False)
cache.create_custom_column('three', 'CC3', 'int', False)
diff --git a/src/calibre/db/tests/utils.py b/src/calibre/db/tests/utils.py
index 0b3d783132..6378cf0a90 100644
--- a/src/calibre/db/tests/utils.py
+++ b/src/calibre/db/tests/utils.py
@@ -27,7 +27,7 @@ class UtilsTest(BaseTest):
total = 0
for i in range(1, num+1):
sz = i * 1000
- c.insert(i, i, (('%d'%i) * sz).encode('ascii'))
+ c.insert(i, i, (f'{i}' * sz).encode('ascii'))
total += sz
return total
@@ -44,7 +44,7 @@ class UtilsTest(BaseTest):
for i in (3, 4, 2, 5, 1):
data, ts = c[i]
self.assertEqual(i, ts, 'timestamp not correct')
- self.assertEqual((('%d'%i) * (i*1000)).encode('ascii'), data)
+ self.assertEqual((f'{i}' * (i*1000)).encode('ascii'), data)
c.set_group_id('a')
self.basic_fill(c)
order = tuple(c.items)
diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py
index 1917d25a8a..e43af82dc1 100644
--- a/src/calibre/db/tests/writing.py
+++ b/src/calibre/db/tests/writing.py
@@ -387,7 +387,7 @@ class WritingTest(BaseTest):
for book_id in book_ids:
raw = cache.read_backup(book_id)
opf = OPF(BytesIO(raw))
- ae(opf.title, 'title%d'%book_id)
+ ae(opf.title, f'title{book_id}')
ae(opf.authors, ['author1', 'author2'])
tested_fields = 'title authors tags'.split()
before = {f:cache.all_field_for(f, book_ids) for f in tested_fields}
@@ -439,9 +439,9 @@ class WritingTest(BaseTest):
ae(cache.set_cover({bid:img for bid in (1, 2, 3)}), {1, 2, 3})
old = self.init_old()
for book_id in (1, 2, 3):
- ae(cache.cover(book_id), img, 'Cover was not set correctly for book %d' % book_id)
+ ae(cache.cover(book_id), img, f'Cover was not set correctly for book {book_id}')
ae(cache.field_for('cover', book_id), 1)
- ae(old.cover(book_id, index_is_id=True), img, 'Cover was not set correctly for book %d' % book_id)
+ ae(old.cover(book_id, index_is_id=True), img, f'Cover was not set correctly for book {book_id}')
self.assertTrue(old.has_cover(book_id))
old.close()
old.break_cycles()
@@ -771,9 +771,9 @@ class WritingTest(BaseTest):
conn.execute('INSERT INTO publishers (name) VALUES ("MŪS")')
uid = conn.last_insert_rowid()
conn.execute('DELETE FROM books_publishers_link')
- conn.execute('INSERT INTO books_publishers_link (book,publisher) VALUES (1, %d)' % lid)
- conn.execute('INSERT INTO books_publishers_link (book,publisher) VALUES (2, %d)' % uid)
- conn.execute('INSERT INTO books_publishers_link (book,publisher) VALUES (3, %d)' % uid)
+ conn.execute(f'INSERT INTO books_publishers_link (book,publisher) VALUES (1, {lid})')
+ conn.execute(f'INSERT INTO books_publishers_link (book,publisher) VALUES (2, {uid})')
+ conn.execute(f'INSERT INTO books_publishers_link (book,publisher) VALUES (3, {uid})')
cache.reload_from_db()
t = cache.fields['publisher'].table
for x in (lid, uid):
diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py
index 13e3d692f5..e51f56c86c 100644
--- a/src/calibre/db/utils.py
+++ b/src/calibre/db/utils.py
@@ -295,9 +295,7 @@ class ThumbnailCache:
self._load_index()
self._invalidate_sizes()
ts = (f'{timestamp:.2f}').replace('.00', '')
- path = '%s%s%s%s%d-%s-%d-%dx%d' % (
- self.group_id, os.sep, book_id % 100, os.sep,
- book_id, ts, len(data), self.thumbnail_size[0], self.thumbnail_size[1])
+ path = f'{self.group_id}{os.sep}{book_id % 100}{os.sep}{book_id}-{ts}-{len(data)}-{self.thumbnail_size[0]}x{self.thumbnail_size[1]}'
path = os.path.join(self.location, path)
key = (self.group_id, book_id)
e = self.items.pop(key, None)
@@ -371,7 +369,7 @@ class ThumbnailCache:
self._remove((self.group_id, book_id))
elif os.path.exists(self.location):
try:
- raw = '\n'.join('%s %d' % (self.group_id, book_id) for book_id in book_ids)
+ raw = '\n'.join(f'{self.group_id} {book_id}' for book_id in book_ids)
with open(os.path.join(self.location, 'invalidate'), 'ab') as f:
f.write(raw.encode('ascii'))
except OSError as err:
diff --git a/src/calibre/devices/kindle/bookmark.py b/src/calibre/devices/kindle/bookmark.py
index 1e013ac06f..b0f2a79897 100644
--- a/src/calibre/devices/kindle/bookmark.py
+++ b/src/calibre/devices/kindle/bookmark.py
@@ -153,7 +153,7 @@ class Bookmark: # {{{
marker_found = 0
text = ''
search_str1 = f'{mi.title}'
- search_str2 = '- Highlight Loc. %d' % (displayed_location)
+ search_str2 = f'- Highlight Loc. {displayed_location}'
for line in f2:
if marker_found == 0:
if line.startswith(search_str1):
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index 206e8f37ac..3d0a403429 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -830,7 +830,7 @@ class KOBO(USBMS):
cursor.close()
def set_readstatus(self, connection, ContentID, ReadStatus):
- debug_print('Kobo::set_readstatus - ContentID=%s, ReadStatus=%d' % (ContentID, ReadStatus))
+ debug_print(f'Kobo::set_readstatus - ContentID={ContentID}, ReadStatus={ReadStatus}')
cursor = connection.cursor()
t = (ContentID,)
cursor.execute('select DateLastRead, ReadStatus from Content where BookID is Null and ContentID = ?', t)
@@ -851,7 +851,7 @@ class KOBO(USBMS):
t = (ReadStatus, datelastread, ContentID,)
try:
- debug_print('Kobo::set_readstatus - Making change - ContentID=%s, ReadStatus=%d, DateLastRead=%s' % (ContentID, ReadStatus, datelastread))
+ debug_print(f'Kobo::set_readstatus - Making change - ContentID={ContentID}, ReadStatus={ReadStatus}, DateLastRead={datelastread}')
cursor.execute("update content set ReadStatus=?,FirstTimeReading='false',DateLastRead=? where BookID is Null and ContentID = ?", t)
except:
debug_print(' Database Exception: Unable to update ReadStatus')
@@ -1742,8 +1742,7 @@ class KOBOTOUCH(KOBO):
if show_debug:
debug_print(f"KoboTouch:update_booklist - title='{title}'", f'ContentType={ContentType}', 'isdownloaded=', isdownloaded)
debug_print(
- ' prefix=%s, DateCreated=%s, readstatus=%d, MimeType=%s, expired=%d, favouritesindex=%d, accessibility=%d, isdownloaded=%s'%
- (prefix, DateCreated, readstatus, MimeType, expired, favouritesindex, accessibility, isdownloaded,))
+ f' prefix={prefix}, DateCreated={DateCreated}, readstatus={readstatus}, MimeType={MimeType}, expired={expired}, favouritesindex={favouritesindex}, accessibility={accessibility}, isdownloaded={isdownloaded}')
changed = False
try:
lpath = path.partition(self.normalize_path(prefix))[2]
@@ -1845,7 +1844,7 @@ class KOBOTOUCH(KOBO):
if idx is not None: # and not (accessibility == 1 and isdownloaded == 'false'):
if show_debug:
self.debug_index = idx
- debug_print('KoboTouch:update_booklist - idx=%d'%idx)
+ debug_print(f'KoboTouch:update_booklist - idx={idx}')
debug_print(f'KoboTouch:update_booklist - lpath={lpath}')
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
@@ -2090,7 +2089,7 @@ class KOBOTOUCH(KOBO):
# self.report_progress((i) / float(books_on_device), _('Getting list of books on device...'))
show_debug = self.is_debugging_title(row['Title'])
if show_debug:
- debug_print('KoboTouch:books - looping on database - row=%d' % i)
+ debug_print(f'KoboTouch:books - looping on database - row={i}')
debug_print("KoboTouch:books - title='{}'".format(row['Title']), 'authors=', row['Attribution'])
debug_print('KoboTouch:books - row=', row)
if not hasattr(row['ContentID'], 'startswith') or row['ContentID'].lower().startswith(
@@ -2506,7 +2505,7 @@ class KOBOTOUCH(KOBO):
if self._card_a_prefix is not None:
ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
else: # ContentType = 16
- debug_print("KoboTouch:contentid_from_path ContentType other than 6 - ContentType='%d'"%ContentType, f"path='{path}'")
+ debug_print(f"KoboTouch:contentid_from_path ContentType other than 6 - ContentType='{ContentType}'", f"path='{path}'")
ContentID = path
ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
if self._card_a_prefix is not None:
@@ -2720,9 +2719,8 @@ class KOBOTOUCH(KOBO):
if not prefs['manage_device_metadata'] == 'manual' and delete_empty_collections:
debug_print('KoboTouch:update_device_database_collections - about to clear empty bookshelves')
self.delete_empty_bookshelves(connection)
- debug_print('KoboTouch:update_device_database_collections - Number of series set=%d Number of books=%d' % (self.series_set, books_in_library))
- debug_print('KoboTouch:update_device_database_collections - Number of core metadata set=%d Number of books=%d' % (
- self.core_metadata_set, books_in_library))
+ debug_print(f'KoboTouch:update_device_database_collections - Number of series set={self.series_set} Number of books={books_in_library}')
+ debug_print(f'KoboTouch:update_device_database_collections - Number of core metadata set={self.core_metadata_set} Number of books={books_in_library}')
self.dump_bookshelves(connection)
@@ -2916,8 +2914,7 @@ class KOBOTOUCH(KOBO):
for ending, cover_options in self.cover_file_endings().items():
kobo_size, min_dbversion, max_dbversion, is_full_size = cover_options
if show_debug:
- debug_print('KoboTouch:_upload_cover - library_cover_size=%s -> kobo_size=%s, min_dbversion=%d max_dbversion=%d, is_full_size=%s' % (
- library_cover_size, kobo_size, min_dbversion, max_dbversion, is_full_size))
+ debug_print(f'KoboTouch:_upload_cover - library_cover_size={library_cover_size} -> kobo_size={kobo_size}, min_dbversion={min_dbversion} max_dbversion={max_dbversion}, is_full_size={is_full_size}')
if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion:
if show_debug:
@@ -4229,7 +4226,7 @@ class KOBOTOUCH(KOBO):
if i == 0:
prints('No shelves found!!')
else:
- prints('Number of shelves=%d'%i)
+ prints(f'Number of shelves={i}')
prints('\nBooks on shelves on device:')
cursor.execute(shelfcontent_query)
@@ -4241,7 +4238,7 @@ class KOBOTOUCH(KOBO):
if i == 0:
prints('No books are on any shelves!!')
else:
- prints('Number of shelved books=%d'%i)
+ prints(f'Number of shelved books={i}')
cursor.close()
debug_print('KoboTouch:dump_bookshelves - end')
diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py
index 115341cb2d..7e0c49885d 100644
--- a/src/calibre/devices/mtp/unix/driver.py
+++ b/src/calibre/devices/mtp/unix/driver.py
@@ -218,7 +218,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.dev = self._filesystem_cache = None
def format_errorstack(self, errs):
- return '\n'.join('%d:%s'%(code, as_unicode(msg)) for code, msg in errs)
+ return '\n'.join(f'{code}:{as_unicode(msg)}' for code, msg in errs)
@synchronous
def open(self, connected_device, library_uuid):
diff --git a/src/calibre/devices/paladin/driver.py b/src/calibre/devices/paladin/driver.py
index 8d799484d5..75c8108a82 100644
--- a/src/calibre/devices/paladin/driver.py
+++ b/src/calibre/devices/paladin/driver.py
@@ -122,7 +122,7 @@ class PALADIN(USBMS):
try:
device_offset = max(time_offsets, key=lambda a: time_offsets.get(a))
- debug_print('Device Offset: %d ms'%device_offset)
+ debug_print(f'Device Offset: {device_offset} ms')
self.device_offset = device_offset
except ValueError:
debug_print('No Books To Detect Device Offset.')
@@ -249,7 +249,7 @@ class PALADIN(USBMS):
sequence_max = sequence_min
sequence_dirty = 0
- debug_print('Book Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Book Sequence Min: {sequence_min}, Source Id: {source_id}')
try:
cursor = connection.cursor()
@@ -283,7 +283,7 @@ class PALADIN(USBMS):
# If the database is 'dirty', then we should fix up the Ids and the sequence number
if sequence_dirty == 1:
- debug_print('Book Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Book Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for book, bookId in db_books.items():
if bookId < sequence_min:
@@ -302,7 +302,7 @@ class PALADIN(USBMS):
cursor.execute(query, t)
self.set_database_sequence_id(connection, 'books', sequence_max)
- debug_print('Book Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Book Sequence Max: {sequence_max}, Source Id: {source_id}')
cursor.close()
return db_books
@@ -355,7 +355,7 @@ class PALADIN(USBMS):
book.mime or mime_type_ext(path_to_ext(lpath)))
cursor.execute(query, t)
book.bookId = connection.last_insert_rowid()
- debug_print('Inserted New Book: (%u) '%book.bookId + book.title)
+ debug_print(f'Inserted New Book: ({book.bookId}) ' + book.title)
else:
query = '''
UPDATE books
@@ -386,7 +386,7 @@ class PALADIN(USBMS):
sequence_max = sequence_min
sequence_dirty = 0
- debug_print('Collection Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Collection Sequence Min: {sequence_min}, Source Id: {source_id}')
try:
cursor = connection.cursor()
@@ -415,7 +415,7 @@ class PALADIN(USBMS):
# If the database is 'dirty', then we should fix up the Ids and the sequence number
if sequence_dirty == 1:
- debug_print('Collection Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Collection Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for collection, collectionId in db_collections.items():
if collectionId < sequence_min:
@@ -434,13 +434,13 @@ class PALADIN(USBMS):
cursor.execute(query, t)
self.set_database_sequence_id(connection, 'tags', sequence_max)
- debug_print('Collection Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Collection Sequence Max: {sequence_max}, Source Id: {source_id}')
# Fix up the collections table now...
sequence_dirty = 0
sequence_max = sequence_min
- debug_print('Collections Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Collections Sequence Min: {sequence_min}, Source Id: {source_id}')
query = 'SELECT _id FROM booktags'
cursor.execute(query)
@@ -454,7 +454,7 @@ class PALADIN(USBMS):
sequence_max = max(sequence_max, row[0])
if sequence_dirty == 1:
- debug_print('Collections Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Collections Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for pairId in db_collection_pairs:
if pairId < sequence_min:
@@ -465,7 +465,7 @@ class PALADIN(USBMS):
sequence_max = sequence_max + 1
self.set_database_sequence_id(connection, 'booktags', sequence_max)
- debug_print('Collections Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Collections Sequence Max: {sequence_max}, Source Id: {source_id}')
cursor.close()
return db_collections
@@ -483,7 +483,7 @@ class PALADIN(USBMS):
t = (collection,)
cursor.execute(query, t)
db_collections[collection] = connection.last_insert_rowid()
- debug_print('Inserted New Collection: (%u) '%db_collections[collection] + collection)
+ debug_print(f'Inserted New Collection: ({db_collections[collection]}) ' + collection)
# Get existing books in collection
query = '''
diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py
index 0e6ed73c3a..c639c78016 100644
--- a/src/calibre/devices/prs505/sony_cache.py
+++ b/src/calibre/devices/prs505/sony_cache.py
@@ -434,8 +434,7 @@ class XMLCache:
book.lpath, book.thumbnail)
self.periodicalize_book(book, ext_record)
- debug_print('Timezone votes: %d GMT, %d LTZ, use_tz_var=%s'%
- (gtz_count, ltz_count, use_tz_var))
+ debug_print(f'Timezone votes: {gtz_count} GMT, {ltz_count} LTZ, use_tz_var={use_tz_var}')
self.update_playlists(i, root, booklist, collections_attributes)
# Update the device collections because update playlist could have added
# some new ones.
diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py
index c3dd753b48..970d99e5f1 100644
--- a/src/calibre/devices/prst1/driver.py
+++ b/src/calibre/devices/prst1/driver.py
@@ -210,7 +210,7 @@ class PRST1(USBMS):
try:
device_offset = max(time_offsets, key=lambda a: time_offsets.get(a))
- debug_print('Device Offset: %d ms'%device_offset)
+ debug_print(f'Device Offset: {device_offset} ms')
self.device_offset = device_offset
except ValueError:
debug_print('No Books To Detect Device Offset.')
@@ -362,7 +362,7 @@ class PRST1(USBMS):
sequence_max = sequence_min
sequence_dirty = 0
- debug_print('Book Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Book Sequence Min: {sequence_min}, Source Id: {source_id}')
try:
cursor = connection.cursor()
@@ -396,7 +396,7 @@ class PRST1(USBMS):
# If the database is 'dirty', then we should fix up the Ids and the sequence number
if sequence_dirty == 1:
- debug_print('Book Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Book Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for book, bookId in db_books.items():
if bookId < sequence_min:
@@ -433,7 +433,7 @@ class PRST1(USBMS):
cursor.execute(query, t)
self.set_database_sequence_id(connection, 'books', sequence_max)
- debug_print('Book Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Book Sequence Max: {sequence_max}, Source Id: {source_id}')
cursor.close()
return db_books
@@ -495,7 +495,7 @@ class PRST1(USBMS):
book.bookId = self.get_lastrowid(cursor)
if upload_covers:
self.upload_book_cover(connection, book, source_id)
- debug_print('Inserted New Book: (%u) '%book.bookId + book.title)
+ debug_print(f'Inserted New Book: ({book.bookId}) ' + book.title)
else:
query = '''
UPDATE books
@@ -534,7 +534,7 @@ class PRST1(USBMS):
sequence_max = sequence_min
sequence_dirty = 0
- debug_print('Collection Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Collection Sequence Min: {sequence_min}, Source Id: {source_id}')
try:
cursor = connection.cursor()
@@ -563,7 +563,7 @@ class PRST1(USBMS):
# If the database is 'dirty', then we should fix up the Ids and the sequence number
if sequence_dirty == 1:
- debug_print('Collection Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Collection Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for collection, collectionId in db_collections.items():
if collectionId < sequence_min:
@@ -582,13 +582,13 @@ class PRST1(USBMS):
cursor.execute(query, t)
self.set_database_sequence_id(connection, 'collection', sequence_max)
- debug_print('Collection Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Collection Sequence Max: {sequence_max}, Source Id: {source_id}')
# Fix up the collections table now...
sequence_dirty = 0
sequence_max = sequence_min
- debug_print('Collections Sequence Min: %d, Source Id: %d'%(sequence_min,source_id))
+ debug_print(f'Collections Sequence Min: {sequence_min}, Source Id: {source_id}')
query = 'SELECT _id FROM collections'
cursor.execute(query)
@@ -602,7 +602,7 @@ class PRST1(USBMS):
sequence_max = max(sequence_max, row[0])
if sequence_dirty == 1:
- debug_print('Collections Sequence Dirty for Source Id: %d'%source_id)
+ debug_print(f'Collections Sequence Dirty for Source Id: {source_id}')
sequence_max = sequence_max + 1
for pairId in db_collection_pairs:
if pairId < sequence_min:
@@ -613,7 +613,7 @@ class PRST1(USBMS):
sequence_max = sequence_max + 1
self.set_database_sequence_id(connection, 'collections', sequence_max)
- debug_print('Collections Sequence Max: %d, Source Id: %d'%(sequence_max,source_id))
+ debug_print(f'Collections Sequence Max: {sequence_max}, Source Id: {source_id}')
cursor.close()
return db_collections
@@ -631,7 +631,7 @@ class PRST1(USBMS):
t = (collection, source_id)
cursor.execute(query, t)
db_collections[collection] = self.get_lastrowid(cursor)
- debug_print('Inserted New Collection: (%u) '%db_collections[collection] + collection)
+ debug_print(f'Inserted New Collection: ({db_collections[collection]}) ' + collection)
# Get existing books in collection
query = '''
diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py
index e9b288d83a..b2d41cf9ce 100644
--- a/src/calibre/devices/scanner.py
+++ b/src/calibre/devices/scanner.py
@@ -219,8 +219,7 @@ def test_for_mem_leak():
for i in range(3):
gc.collect()
usedmem = memory(startmem)
- prints('Memory used in %d repetitions of scan(): %.5f KB'%(reps,
- 1024*usedmem))
+ prints(f'Memory used in {reps} repetitions of scan(): {1024 * usedmem:.5f} KB')
prints('Differences in python object counts:')
diff_hists(h1, gc_histogram())
prints()
diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py
index 1ff1a6285a..7e0bdd6e2e 100644
--- a/src/calibre/devices/smart_device_app/driver.py
+++ b/src/calibre/devices/smart_device_app/driver.py
@@ -853,7 +853,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
json_metadata[key]['book'] = self.json_codec.encode_book_metadata(book['book'])
json_metadata[key]['last_used'] = book['last_used']
result = as_bytes(json.dumps(json_metadata, indent=2, default=to_json))
- fd.write(('%0.7d\n'%(len(result)+1)).encode('ascii'))
+ fd.write(f'{len(result) + 1:007}\n'.encode('ascii'))
fd.write(result)
fd.write(b'\n')
count += 1
@@ -1943,7 +1943,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
try:
self.listen_socket.listen(1)
except:
- message = 'listen on port %d failed' % port
+ message = f'listen on port {port} failed'
self._debug(message)
self._close_listen_socket()
return message
diff --git a/src/calibre/devices/udisks.py b/src/calibre/devices/udisks.py
index a53f172164..c8e5ee00b1 100644
--- a/src/calibre/devices/udisks.py
+++ b/src/calibre/devices/udisks.py
@@ -28,7 +28,7 @@ def node_mountpoint(node):
def basic_mount_options():
- return ['rw', 'noexec', 'nosuid', 'nodev', 'uid=%d'%os.geteuid(), 'gid=%d'%os.getegid()]
+ return ['rw', 'noexec', 'nosuid', 'nodev', f'uid={os.geteuid()}', f'gid={os.getegid()}']
class UDisks:
diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py
index 24512f0769..9b8f0feeaa 100644
--- a/src/calibre/devices/usbms/device.py
+++ b/src/calibre/devices/usbms/device.py
@@ -597,7 +597,7 @@ class Device(DeviceConfig, DevicePlugin):
continue
mp, ret = mount(card, typ)
if mp is None:
- print('Unable to mount card (Error code: %d)'%ret, file=sys.stderr)
+ print(f'Unable to mount card (Error code: {ret})', file=sys.stderr)
else:
if not mp.endswith('/'):
mp += '/'
diff --git a/src/calibre/devices/winusb.py b/src/calibre/devices/winusb.py
index bc5569a965..7b70938fc9 100644
--- a/src/calibre/devices/winusb.py
+++ b/src/calibre/devices/winusb.py
@@ -179,10 +179,7 @@ class USB_DEVICE_DESCRIPTOR(Structure):
)
def __repr__(self):
- return 'USBDevice(class=0x%x sub_class=0x%x protocol=0x%x vendor_id=0x%x product_id=0x%x bcd=0x%x manufacturer=%d product=%d serial_number=%d)' % (
- self.bDeviceClass, self.bDeviceSubClass, self.bDeviceProtocol,
- self.idVendor, self.idProduct, self.bcdDevice, self.iManufacturer,
- self.iProduct, self.iSerialNumber)
+ return f'USBDevice(class=0x{self.bDeviceClass:x} sub_class=0x{self.bDeviceSubClass:x} protocol=0x{self.bDeviceProtocol:x} vendor_id=0x{self.idVendor:x} product_id=0x{self.idProduct:x} bcd=0x{self.bcdDevice:x} manufacturer={self.iManufacturer} product={self.iProduct} serial_number={self.iSerialNumber})'
class USB_ENDPOINT_DESCRIPTOR(Structure):
@@ -935,7 +932,7 @@ def get_usb_info(usbdev, debug=False): # {{{
# randomly after some time of my Kindle being
# connected. Disconnecting and reconnecting causes
# it to start working again.
- prints('Failed to read %s from device, with error: [%d] %s' % (name, err.winerror, as_unicode(err)))
+ prints(f'Failed to read {name} from device, with error: [{err.winerror}] {as_unicode(err)}')
finally:
CloseHandle(handle)
return ans
diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py
index d89b103731..6771bdc42d 100644
--- a/src/calibre/ebooks/comic/input.py
+++ b/src/calibre/ebooks/comic/input.py
@@ -236,7 +236,7 @@ class PageProcessor(list): # {{{
final_fmt = QImage.Format.Format_Indexed8 if uses_256_colors else QImage.Format.Format_Grayscale16
if img.format() != final_fmt:
img = img.convertToFormat(final_fmt)
- dest = '%d_%d.%s'%(self.num, i, self.opts.output_format)
+ dest = f'{self.num}_{i}.{self.opts.output_format}'
dest = os.path.join(self.dest, dest)
with open(dest, 'wb') as f:
f.write(image_to_data(img, fmt=self.opts.output_format))
diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py
index 4f8a8cf202..cba93eadbf 100644
--- a/src/calibre/ebooks/conversion/cli.py
+++ b/src/calibre/ebooks/conversion/cli.py
@@ -302,7 +302,7 @@ class ProgressBar:
def __call__(self, frac, msg=''):
if msg:
percent = int(frac*100)
- self.log('%d%% %s'%(percent, msg))
+ self.log(f'{percent}% {msg}')
def create_option_parser(args, log):
diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py
index 80b279ea95..d4914fd8b2 100644
--- a/src/calibre/ebooks/conversion/plugins/chm_input.py
+++ b/src/calibre/ebooks/conversion/plugins/chm_input.py
@@ -131,7 +131,7 @@ class CHMInput(InputFormatPlugin):
# print('Printing hhcroot')
# print(etree.tostring(hhcroot, pretty_print=True))
# print('=============================')
- log.debug('Found %d section nodes' % toc.count())
+ log.debug(f'Found {toc.count()} section nodes')
htmlpath = os.path.splitext(hhcpath)[0] + '.html'
base = os.path.dirname(os.path.abspath(htmlpath))
diff --git a/src/calibre/ebooks/conversion/plugins/comic_input.py b/src/calibre/ebooks/conversion/plugins/comic_input.py
index c2318cdc94..65c1237236 100644
--- a/src/calibre/ebooks/conversion/plugins/comic_input.py
+++ b/src/calibre/ebooks/conversion/plugins/comic_input.py
@@ -175,7 +175,7 @@ class ComicInput(InputFormatPlugin):
num_pages_per_comic = []
for i, x in enumerate(comics_):
title, fname = x
- cdir = 'comic_%d'%(i+1) if len(comics_) > 1 else '.'
+ cdir = f'comic_{i + 1}' if len(comics_) > 1 else '.'
cdir = os.path.abspath(cdir)
if not os.path.exists(cdir):
os.makedirs(cdir)
@@ -228,11 +228,11 @@ class ComicInput(InputFormatPlugin):
wrapper_page_href = href(wrappers[0])
for i in range(num_pages_per_comic[0]):
toc.add_item(f'{wrapper_page_href}#page_{i+1}', None,
- _('Page')+' %d'%(i+1), play_order=i)
+ _('Page')+f' {i + 1}', play_order=i)
else:
for i, x in enumerate(wrappers):
- toc.add_item(href(x), None, _('Page')+' %d'%(i+1),
+ toc.add_item(href(x), None, _('Page')+f' {i + 1}',
play_order=i)
else:
po = 0
@@ -246,12 +246,12 @@ class ComicInput(InputFormatPlugin):
wrapper_page_href = href(wrappers[0])
for i in range(num_pages):
stoc.add_item(f'{wrapper_page_href}#page_{i+1}', None,
- _('Page')+' %d'%(i+1), play_order=po)
+ _('Page')+f' {i + 1}', play_order=po)
po += 1
else:
for i, x in enumerate(wrappers):
stoc.add_item(href(x), None,
- _('Page')+' %d'%(i+1), play_order=po)
+ _('Page')+f' {i + 1}', play_order=po)
po += 1
opf.set_toc(toc)
with open('metadata.opf', 'wb') as m, open('toc.ncx', 'wb') as n:
@@ -282,7 +282,7 @@ class ComicInput(InputFormatPlugin):
dir = os.path.dirname(pages[0])
for i, page in enumerate(pages):
wrapper = WRAPPER%(XHTML_NS, i+1, os.path.basename(page), i+1)
- page = os.path.join(dir, 'page_%d.xhtml'%(i+1))
+ page = os.path.join(dir, f'page_{i + 1}.xhtml')
with open(page, 'wb') as f:
f.write(wrapper.encode('utf-8'))
wrappers.append(page)
diff --git a/src/calibre/ebooks/conversion/plugins/djvu_input.py b/src/calibre/ebooks/conversion/plugins/djvu_input.py
index 34587376ed..ab737d4e60 100644
--- a/src/calibre/ebooks/conversion/plugins/djvu_input.py
+++ b/src/calibre/ebooks/conversion/plugins/djvu_input.py
@@ -41,7 +41,7 @@ class DJVUInput(InputFormatPlugin):
c = 0
while os.path.exists(htmlfile):
c += 1
- htmlfile = os.path.join(base, 'index%d.html'%c)
+ htmlfile = os.path.join(base, f'index{c}.html')
with open(htmlfile, 'wb') as f:
f.write(html.encode('utf-8'))
odi = options.debug_pipeline
diff --git a/src/calibre/ebooks/conversion/plugins/fb2_input.py b/src/calibre/ebooks/conversion/plugins/fb2_input.py
index 3436360ba2..9a00be2f5e 100644
--- a/src/calibre/ebooks/conversion/plugins/fb2_input.py
+++ b/src/calibre/ebooks/conversion/plugins/fb2_input.py
@@ -110,10 +110,10 @@ class FB2Input(InputFormatPlugin):
note = notes.get(cite, None)
if note:
c = 1
- while 'cite%d' % c in all_ids:
+ while f'cite{c}' in all_ids:
c += 1
if not note.get('id', None):
- note.set('id', 'cite%d' % c)
+ note.set('id', f'cite{c}')
all_ids.add(note.get('id'))
a.set('href', '#{}'.format(note.get('id')))
for x in result.xpath('//*[@link_note or @link_cite]'):
diff --git a/src/calibre/ebooks/conversion/plugins/htmlz_input.py b/src/calibre/ebooks/conversion/plugins/htmlz_input.py
index 88a0edf03a..1a0df9cfcb 100644
--- a/src/calibre/ebooks/conversion/plugins/htmlz_input.py
+++ b/src/calibre/ebooks/conversion/plugins/htmlz_input.py
@@ -89,7 +89,7 @@ class HTMLZInput(InputFormatPlugin):
c = 0
while os.path.exists(htmlfile):
c += 1
- htmlfile = 'index%d.html'%c
+ htmlfile = f'index{c}.html'
with open(htmlfile, 'wb') as f:
f.write(html.encode('utf-8'))
odi = options.debug_pipeline
diff --git a/src/calibre/ebooks/conversion/plugins/rtf_input.py b/src/calibre/ebooks/conversion/plugins/rtf_input.py
index 56f7a50f7d..619207852b 100644
--- a/src/calibre/ebooks/conversion/plugins/rtf_input.py
+++ b/src/calibre/ebooks/conversion/plugins/rtf_input.py
@@ -141,7 +141,7 @@ class RTFInput(InputFormatPlugin):
if fmt is None:
fmt = 'wmf'
count += 1
- name = '%04d.%s' % (count, fmt)
+ name = f'{count:04}.{fmt}'
with open(name, 'wb') as f:
f.write(data)
imap[count] = name
@@ -243,7 +243,7 @@ class RTFInput(InputFormatPlugin):
if style not in border_styles:
border_styles.append(style)
idx = border_styles.index(style)
- cls = 'border_style%d'%idx
+ cls = f'border_style{idx}'
style_map[cls] = style
elem.set('class', cls)
return style_map
diff --git a/src/calibre/ebooks/conversion/plugins/snb_input.py b/src/calibre/ebooks/conversion/plugins/snb_input.py
index 64273cc2e7..af5f7e625d 100644
--- a/src/calibre/ebooks/conversion/plugins/snb_input.py
+++ b/src/calibre/ebooks/conversion/plugins/snb_input.py
@@ -90,7 +90,7 @@ class SNBInput(InputFormatPlugin):
for ch in toc.find('.//body'):
chapterName = ch.text
chapterSrc = ch.get('src')
- fname = 'ch_%d.htm' % i
+ fname = f'ch_{i}.htm'
data = snbFile.GetFileStream('snbc/' + chapterSrc)
if data is None:
continue
diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py
index 1a01cc570e..453462b179 100644
--- a/src/calibre/ebooks/conversion/preprocess.py
+++ b/src/calibre/ebooks/conversion/preprocess.py
@@ -498,7 +498,7 @@ class HTMLPreProcessor:
# search / replace using the sr?_search / sr?_replace options
for i in range(1, 4):
- search, replace = 'sr%d_search'%i, 'sr%d_replace'%i
+ search, replace = f'sr{i}_search', f'sr{i}_replace'
search_pattern = getattr(self.extra_opts, search, '')
replace_txt = getattr(self.extra_opts, replace, '')
if search_pattern:
@@ -559,7 +559,7 @@ class HTMLPreProcessor:
name, i = None, 0
while not name or os.path.exists(os.path.join(odir, name)):
i += 1
- name = '%04d.html'%i
+ name = f'{i:04}.html'
with open(os.path.join(odir, name), 'wb') as f:
f.write(raw.encode('utf-8'))
diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py
index 1db3b436f7..861ae760b2 100644
--- a/src/calibre/ebooks/conversion/utils.py
+++ b/src/calibre/ebooks/conversion/utils.py
@@ -140,7 +140,7 @@ class HeuristicProcessor:
name, i = None, 0
while not name or os.path.exists(os.path.join(odir, name)):
i += 1
- name = '%04d.html'%i
+ name = f'{i:04}.html'
with open(os.path.join(odir, name), 'wb') as f:
f.write(raw.encode('utf-8'))
diff --git a/src/calibre/ebooks/djvu/djvu.py b/src/calibre/ebooks/djvu/djvu.py
index f7124d7349..2e035729ad 100644
--- a/src/calibre/ebooks/djvu/djvu.py
+++ b/src/calibre/ebooks/djvu/djvu.py
@@ -45,8 +45,7 @@ class DjvuChunk:
print('found', self.type, self.subtype, pos, self.size)
if self.type in b'FORM'.split():
if verbose > 0:
- print('processing substuff %d %d (%x)' % (pos, self.dataend,
- self.dataend))
+ print(f'processing substuff {pos} {self.dataend} ({self.dataend:x})')
numchunks = 0
while pos < self.dataend:
x = DjvuChunk(buf, pos, start+self.size, verbose=verbose)
@@ -54,11 +53,10 @@ class DjvuChunk:
self._subchunks.append(x)
newpos = pos + x.size + x.headersize + (1 if (x.size % 2) else 0)
if verbose > 0:
- print('newpos %d %d (%x, %x) %d' % (newpos, self.dataend,
- newpos, self.dataend, x.headersize))
+ print(f'newpos {newpos} {self.dataend} ({newpos:x}, {self.dataend:x}) {x.headersize}')
pos = newpos
if verbose > 0:
- print(' end of chunk %d (%x)' % (pos, pos))
+ print(f' end of chunk {pos} ({pos:x})')
def dump(self, verbose=0, indent=1, out=None, txtout=None, maxlevel=100):
if out:
diff --git a/src/calibre/ebooks/docx/cleanup.py b/src/calibre/ebooks/docx/cleanup.py
index 02da00c739..26addea801 100644
--- a/src/calibre/ebooks/docx/cleanup.py
+++ b/src/calibre/ebooks/docx/cleanup.py
@@ -155,7 +155,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid):
# Process dir attributes
class_map = dict(itervalues(styles.classes))
- parents = ('p', 'div') + tuple('h%d' % i for i in range(1, 7))
+ parents = ('p', 'div') + tuple(f'h{i}' for i in range(1, 7))
for parent in root.xpath('//*[({})]'.format(' or '.join(f'name()="{t}"' for t in parents))):
# Ensure that children of rtl parents that are not rtl have an
# explicit dir set. Also, remove dir from children if it is the same as
diff --git a/src/calibre/ebooks/docx/fields.py b/src/calibre/ebooks/docx/fields.py
index 57eb184a51..15746029b7 100644
--- a/src/calibre/ebooks/docx/fields.py
+++ b/src/calibre/ebooks/docx/fields.py
@@ -110,7 +110,7 @@ class Fields:
c = 0
while self.index_bookmark_prefix in all_ids:
c += 1
- self.index_bookmark_prefix = self.index_bookmark_prefix.replace('-', '%d-' % c)
+ self.index_bookmark_prefix = self.index_bookmark_prefix.replace('-', f'{c}-')
stack = []
for elem in self.namespace.XPath(
'//*[name()="w:p" or name()="w:r" or'
@@ -209,7 +209,7 @@ class Fields:
def WORD(x):
return self.namespace.expand('w:' + x)
self.index_bookmark_counter += 1
- bmark = xe['anchor'] = '%s%d' % (self.index_bookmark_prefix, self.index_bookmark_counter)
+ bmark = xe['anchor'] = f'{self.index_bookmark_prefix}{self.index_bookmark_counter}'
p = field.start.getparent()
bm = p.makeelement(WORD('bookmarkStart'))
bm.set(WORD('id'), bmark), bm.set(WORD('name'), bmark)
diff --git a/src/calibre/ebooks/docx/footnotes.py b/src/calibre/ebooks/docx/footnotes.py
index a045cd30c9..0d39e67975 100644
--- a/src/calibre/ebooks/docx/footnotes.py
+++ b/src/calibre/ebooks/docx/footnotes.py
@@ -48,7 +48,7 @@ class Footnotes:
note = notes.get(fid, None)
if note is not None and note.type == 'normal':
self.counter += 1
- anchor = 'note_%d' % self.counter
+ anchor = f'note_{self.counter}'
self.notes[anchor] = (str(self.counter), note)
return anchor, str(self.counter)
return None, None
diff --git a/src/calibre/ebooks/docx/images.py b/src/calibre/ebooks/docx/images.py
index ff429252d4..f5f790d7ab 100644
--- a/src/calibre/ebooks/docx/images.py
+++ b/src/calibre/ebooks/docx/images.py
@@ -183,7 +183,7 @@ class Images:
name = base
while name in exists:
n, e = base.rpartition('.')[0::2]
- name = '%s-%d.%s' % (n, c, e)
+ name = f'{n}-{c}.{e}'
c += 1
return name
@@ -191,7 +191,7 @@ class Images:
resized, img = resize_to_fit(raw, max_width, max_height)
if resized:
base, ext = os.path.splitext(base)
- base = base + '-%dx%d%s' % (max_width, max_height, ext)
+ base = base + f'-{max_width}x{max_height}{ext}'
raw = image_to_data(img, fmt=ext[1:])
return raw, base, resized
diff --git a/src/calibre/ebooks/docx/names.py b/src/calibre/ebooks/docx/names.py
index def15f0f7f..d9f20ec91d 100644
--- a/src/calibre/ebooks/docx/names.py
+++ b/src/calibre/ebooks/docx/names.py
@@ -93,7 +93,7 @@ def generate_anchor(name, existing):
x = y = 'id_' + re.sub(r'[^0-9a-zA-Z_]', '', ascii_text(name)).lstrip('_')
c = 1
while y in existing:
- y = '%s_%d' % (x, c)
+ y = f'{x}_{c}'
c += 1
return y
diff --git a/src/calibre/ebooks/docx/numbering.py b/src/calibre/ebooks/docx/numbering.py
index a99402648f..6d19c75c19 100644
--- a/src/calibre/ebooks/docx/numbering.py
+++ b/src/calibre/ebooks/docx/numbering.py
@@ -40,7 +40,7 @@ def alphabet(val, lower=True):
alphabet_map = {
'lower-alpha':alphabet, 'upper-alpha':partial(alphabet, lower=False),
'lower-roman':lambda x: roman(x).lower(), 'upper-roman':roman,
- 'decimal-leading-zero': lambda x: '0%d' % x
+ 'decimal-leading-zero': lambda x: f'0{x}'
}
@@ -73,7 +73,7 @@ class Level:
if x > ilvl or x not in counter:
return ''
val = counter[x] - (0 if x == ilvl else 1)
- formatter = alphabet_map.get(self.fmt, lambda x: '%d' % x)
+ formatter = alphabet_map.get(self.fmt, lambda x: f'{x}')
return formatter(val)
return re.sub(r'%(\d+)', sub, template).rstrip() + '\xa0'
diff --git a/src/calibre/ebooks/docx/styles.py b/src/calibre/ebooks/docx/styles.py
index 01557f201e..9d6143a708 100644
--- a/src/calibre/ebooks/docx/styles.py
+++ b/src/calibre/ebooks/docx/styles.py
@@ -427,7 +427,7 @@ class Styles:
ans, _ = self.classes.get(h, (None, None))
if ans is None:
self.counter[prefix] += 1
- ans = '%s_%d' % (prefix, self.counter[prefix])
+ ans = f'{prefix}_{self.counter[prefix]}'
self.classes[h] = (ans, css)
return ans
diff --git a/src/calibre/ebooks/docx/tables.py b/src/calibre/ebooks/docx/tables.py
index 61a8ad31c3..fd78997ec6 100644
--- a/src/calibre/ebooks/docx/tables.py
+++ b/src/calibre/ebooks/docx/tables.py
@@ -460,9 +460,9 @@ class Table:
return (m - (m % n)) // n
if c is not None:
odd_column_band = (divisor(c, self.table_style.col_band_size) % 2) == 1
- overrides.append('band%dVert' % (1 if odd_column_band else 2))
+ overrides.append(f'band{1 if odd_column_band else 2}Vert')
odd_row_band = (divisor(r, self.table_style.row_band_size) % 2) == 1
- overrides.append('band%dHorz' % (1 if odd_row_band else 2))
+ overrides.append(f'band{1 if odd_row_band else 2}Horz')
# According to the OOXML spec columns should have higher override
# priority than rows, but Word seems to do it the other way around.
diff --git a/src/calibre/ebooks/docx/to_html.py b/src/calibre/ebooks/docx/to_html.py
index fc4fd89e9f..554da8ad0a 100644
--- a/src/calibre/ebooks/docx/to_html.py
+++ b/src/calibre/ebooks/docx/to_html.py
@@ -518,7 +518,7 @@ class Convert:
m = re.match(r'heading\s+(\d+)$', style.style_name or '', re.IGNORECASE)
if m is not None:
n = min(6, max(1, int(m.group(1))))
- dest.tag = 'h%d' % n
+ dest.tag = f'h{n}'
dest.set('data-heading-level', str(n))
if style.bidi is True:
diff --git a/src/calibre/ebooks/docx/toc.py b/src/calibre/ebooks/docx/toc.py
index 340515e7da..cd11501e67 100644
--- a/src/calibre/ebooks/docx/toc.py
+++ b/src/calibre/ebooks/docx/toc.py
@@ -30,7 +30,7 @@ def from_headings(body, log, namespace, num_levels=3):
def ensure_id(elem):
ans = elem.get('id', None)
if not ans:
- ans = 'toc_id_%d' % (next(idcount) + 1)
+ ans = f'toc_id_{next(idcount) + 1}'
elem.set('id', ans)
return ans
diff --git a/src/calibre/ebooks/docx/writer/container.py b/src/calibre/ebooks/docx/writer/container.py
index 52c5ef8bdb..19c11404ab 100644
--- a/src/calibre/ebooks/docx/writer/container.py
+++ b/src/calibre/ebooks/docx/writer/container.py
@@ -134,7 +134,7 @@ class DocumentRelationships:
def add_relationship(self, target, rtype, target_mode=None):
ans = self.get_relationship_id(target, rtype, target_mode)
if ans is None:
- ans = 'rId%d' % (len(self.rmap) + 1)
+ ans = f'rId{len(self.rmap) + 1}'
self.rmap[(target, rtype, target_mode)] = ans
return ans
diff --git a/src/calibre/ebooks/docx/writer/fonts.py b/src/calibre/ebooks/docx/writer/fonts.py
index f6c2489312..ae63567d78 100644
--- a/src/calibre/ebooks/docx/writer/fonts.py
+++ b/src/calibre/ebooks/docx/writer/fonts.py
@@ -67,8 +67,8 @@ class FontsManager:
item = ef['item']
rid = rel_map.get(item)
if rid is None:
- rel_map[item] = rid = 'rId%d' % num
- fname = 'fonts/font%d.odttf' % num
+ rel_map[item] = rid = f'rId{num}'
+ fname = f'fonts/font{num}.odttf'
makeelement(embed_relationships, 'Relationship', Id=rid, Type=self.namespace.names['EMBEDDED_FONT'], Target=fname)
font_data_map['word/' + fname] = obfuscate_font_data(item.data, key)
makeelement(font, 'w:embed' + tag, r_id=rid,
diff --git a/src/calibre/ebooks/docx/writer/links.py b/src/calibre/ebooks/docx/writer/links.py
index a6f9a0f17e..77fbc4b4dc 100644
--- a/src/calibre/ebooks/docx/writer/links.py
+++ b/src/calibre/ebooks/docx/writer/links.py
@@ -92,7 +92,7 @@ class LinksManager:
i, bname = 0, name
while name in self.used_bookmark_names:
i += 1
- name = bname + ('_%d' % i)
+ name = bname + f'_{i}'
self.anchor_map[key] = name
self.used_bookmark_names.add(name)
return name
diff --git a/src/calibre/ebooks/docx/writer/lists.py b/src/calibre/ebooks/docx/writer/lists.py
index aced84081b..4cb4bb6dc3 100644
--- a/src/calibre/ebooks/docx/writer/lists.py
+++ b/src/calibre/ebooks/docx/writer/lists.py
@@ -84,7 +84,7 @@ class NumberingDefinition:
makeelement = self.namespace.makeelement
an = makeelement(parent, 'w:abstractNum', w_abstractNumId=str(self.num_id))
makeelement(an, 'w:multiLevelType', w_val='hybridMultilevel')
- makeelement(an, 'w:name', w_val='List %d' % (self.num_id + 1))
+ makeelement(an, 'w:name', w_val=f'List {self.num_id + 1}')
for level in self.levels:
level.serialize(an, makeelement)
diff --git a/src/calibre/ebooks/docx/writer/styles.py b/src/calibre/ebooks/docx/writer/styles.py
index c7cdc1cd18..2d1efef1e8 100644
--- a/src/calibre/ebooks/docx/writer/styles.py
+++ b/src/calibre/ebooks/docx/writer/styles.py
@@ -744,7 +744,7 @@ class StylesManager:
if style.outline_level is None:
val = f'Para %0{snum}d' % i
else:
- val = 'Heading %d' % (style.outline_level + 1)
+ val = f'Heading {style.outline_level + 1}'
heading_styles.append(style)
style.id = style.name = val
style.seq = i
@@ -764,7 +764,7 @@ class StylesManager:
ds_counts[run.descendant_style] += run.style_weight
rnum = len(str(max(1, len(ds_counts) - 1)))
for i, (text_style, count) in enumerate(ds_counts.most_common()):
- text_style.id = 'Text%d' % i
+ text_style.id = f'Text{i}'
text_style.name = f'%0{rnum}d Text' % i
text_style.seq = i
self.descendant_text_styles = sorted(descendant_style_map, key=attrgetter('seq'))
diff --git a/src/calibre/ebooks/epub/pages.py b/src/calibre/ebooks/epub/pages.py
index bfccca2090..e1967d539f 100644
--- a/src/calibre/ebooks/epub/pages.py
+++ b/src/calibre/ebooks/epub/pages.py
@@ -48,7 +48,7 @@ def add_page_map(opfpath, opts):
oeb = OEBBook(opfpath)
selector = XPath(opts.page, namespaces=NSMAP)
name_for = build_name_for(opts.page_names)
- idgen = ('calibre-page-%d' % n for n in count(1))
+ idgen = (f'calibre-page-{n}' for n in count(1))
for item in oeb.spine:
data = item.data
for elem in selector(data):
diff --git a/src/calibre/ebooks/epub/periodical.py b/src/calibre/ebooks/epub/periodical.py
index 80db09a5e9..f4f599ef8c 100644
--- a/src/calibre/ebooks/epub/periodical.py
+++ b/src/calibre/ebooks/epub/periodical.py
@@ -137,7 +137,7 @@ def sony_metadata(oeb):
for i, section in enumerate(toc):
if not section.href:
continue
- secid = 'section%d'%i
+ secid = f'section{i}'
sectitle = section.title
if not sectitle:
sectitle = _('Unknown')
@@ -170,7 +170,7 @@ def sony_metadata(oeb):
desc = section.description
if not desc:
desc = ''
- aid = 'article%d'%j
+ aid = f'article{j}'
entries.append(SONY_ATOM_ENTRY.format(
title=xml(atitle),
diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py
index 4f9f883777..b5bd4af51a 100644
--- a/src/calibre/ebooks/fb2/fb2ml.py
+++ b/src/calibre/ebooks/fb2/fb2ml.py
@@ -116,7 +116,7 @@ class FB2MLizer:
metadata['title'] = self.oeb_book.metadata.title[0].value
metadata['appname'] = __appname__
metadata['version'] = __version__
- metadata['date'] = '%i.%i.%i' % (datetime.now().day, datetime.now().month, datetime.now().year)
+ metadata['date'] = f'{datetime.now().day}.{datetime.now().month}.{datetime.now().year}'
if self.oeb_book.metadata.language:
lc = lang_as_iso639_1(self.oeb_book.metadata.language[0].value)
if not lc:
diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py
index 9f581808ad..8ae46fa9a5 100644
--- a/src/calibre/ebooks/html/input.py
+++ b/src/calibre/ebooks/html/input.py
@@ -153,7 +153,7 @@ class HTMLFile:
return hash(self.path)
def __str__(self):
- return 'HTMLFile:%d:%s:%r'%(self.level, 'b' if self.is_binary else 'a', self.path)
+ return f"HTMLFile:{self.level}:{'b' if self.is_binary else 'a'}:{self.path!r}"
def __repr__(self):
return str(self)
diff --git a/src/calibre/ebooks/lit/reader.py b/src/calibre/ebooks/lit/reader.py
index 15dc7be53e..4af6a3b118 100644
--- a/src/calibre/ebooks/lit/reader.py
+++ b/src/calibre/ebooks/lit/reader.py
@@ -242,7 +242,7 @@ class UnBinary:
if flags & FLAG_ATOM:
if not self.tag_atoms or tag not in self.tag_atoms:
raise LitError(
- 'atom tag %d not in atom tag list' % tag)
+ f'atom tag {tag} not in atom tag list')
tag_name = self.tag_atoms[tag]
current_map = self.attr_atoms
elif tag < len(self.tag_map):
@@ -257,8 +257,7 @@ class UnBinary:
buf.write(encode(tag_name))
elif flags & FLAG_CLOSING:
if depth == 0:
- raise LitError('Extra closing tag %s at %d'%(tag_name,
- self.cpos))
+ raise LitError(f'Extra closing tag {tag_name} at {self.cpos}')
break
elif state == 'get attr':
@@ -290,7 +289,7 @@ class UnBinary:
attr = self.attr_map[oc]
if not attr or not isinstance(attr, string_or_bytes):
raise LitError(
- 'Unknown attribute %d in tag %s' % (oc, tag_name))
+ f'Unknown attribute {oc} in tag {tag_name}')
if attr.startswith('%'):
in_censorship = True
state = 'get value length'
@@ -315,7 +314,7 @@ class UnBinary:
if oc == 0xffff:
continue
if count < 0 or count > (len(bin) - self.cpos):
- raise LitError('Invalid character count %d' % count)
+ raise LitError(f'Invalid character count {count}')
elif state == 'get value':
if count == 0xfffe:
@@ -342,7 +341,7 @@ class UnBinary:
elif state == 'get custom length':
count = oc - 1
if count <= 0 or count > len(bin)-self.cpos:
- raise LitError('Invalid character count %d' % count)
+ raise LitError(f'Invalid character count {count}')
dynamic_tag += 1
state = 'get custom'
tag_name = ''
@@ -357,7 +356,7 @@ class UnBinary:
elif state == 'get attr length':
count = oc - 1
if count <= 0 or count > (len(bin) - self.cpos):
- raise LitError('Invalid character count %d' % count)
+ raise LitError(f'Invalid character count {count}')
buf.write(b' ')
state = 'get custom attr'
@@ -371,7 +370,7 @@ class UnBinary:
elif state == 'get href length':
count = oc - 1
if count <= 0 or count > (len(bin) - self.cpos):
- raise LitError('Invalid character count %d' % count)
+ raise LitError(f'Invalid character count {count}')
href = ''
state = 'get href'
@@ -397,8 +396,7 @@ class DirectoryEntry:
self.size = size
def __repr__(self):
- return 'DirectoryEntry(name=%s, section=%d, offset=%d, size=%d)' \
- % (repr(self.name), self.section, self.offset, self.size)
+ return f'DirectoryEntry(name={repr(self.name)}, section={self.section}, offset={self.offset}, size={self.size})'
def __str__(self):
return repr(self)
@@ -429,9 +427,7 @@ class ManifestItem:
return self.internal == other
def __repr__(self):
- return (
- 'ManifestItem(internal=%r, path=%r, mime_type=%r, offset=%d, root=%r, state=%r)'
- ) % (self.internal, self.path, self.mime_type, self.offset, self.root, self.state)
+ return f"ManifestItem(internal={self.internal!r}, path={self.path!r}, mime_type={self.mime_type!r}, offset={self.offset}, root={self.root!r}, state={self.state!r})"
def preserve(function):
@@ -462,7 +458,7 @@ class LitFile:
if self.magic != b'ITOLITLS':
raise LitError('Not a valid LIT file')
if self.version != 1:
- raise LitError('Unknown LIT version %d' % (self.version,))
+ raise LitError(f'Unknown LIT version {self.version}')
self.read_secondary_header()
self.read_header_pieces()
self.read_section_names()
@@ -553,7 +549,7 @@ class LitFile:
if blocktype == b'CAOL':
if blockver != 2:
raise LitError(
- 'Unknown CAOL block format %d' % blockver)
+ f'Unknown CAOL block format {blockver}')
self.creator_id = u32(byts[offset+12:])
self.entry_chunklen = u32(byts[offset+20:])
self.count_chunklen = u32(byts[offset+24:])
@@ -563,7 +559,7 @@ class LitFile:
elif blocktype == b'ITSF':
if blockver != 4:
raise LitError(
- 'Unknown ITSF block format %d' % blockver)
+ f'Unknown ITSF block format {blockver}')
if u32(byts[offset+4+16:]):
raise LitError('This file has a 64bit content offset')
self.content_offset = u32(byts[offset+16:])
diff --git a/src/calibre/ebooks/lrf/input.py b/src/calibre/ebooks/lrf/input.py
index ca1d0c345e..754838a126 100644
--- a/src/calibre/ebooks/lrf/input.py
+++ b/src/calibre/ebooks/lrf/input.py
@@ -138,9 +138,9 @@ class TextBlock(etree.XSLTExtension):
classes = []
bs = node.get('blockstyle')
if bs in self.styles.block_style_map:
- classes.append('bs%d'%self.styles.block_style_map[bs])
+ classes.append(f'bs{self.styles.block_style_map[bs]}')
if ts in self.styles.text_style_map:
- classes.append('ts%d'%self.styles.text_style_map[ts])
+ classes.append(f'ts{self.styles.text_style_map[ts]}')
if classes:
root.set('class', ' '.join(classes))
objid = node.get('objid', None)
@@ -218,7 +218,7 @@ class TextBlock(etree.XSLTExtension):
def process_container(self, child, tgt):
idx = self.styles.get_text_styles(child)
if idx is not None:
- tgt.set('class', 'ts%d'%idx)
+ tgt.set('class', f'ts{idx}')
self.parent.append(tgt)
orig_parent = self.parent
self.parent = tgt
@@ -305,7 +305,7 @@ class Styles(etree.XSLTExtension):
for i, s in enumerate(w):
if not s:
continue
- rsel = '.%s%d'%(sel, i)
+ rsel = f'.{sel}{i}'
s = join(s)
f.write(as_bytes(rsel + ' {\n\t' + s + '\n}\n\n'))
@@ -331,8 +331,8 @@ class Styles(etree.XSLTExtension):
if a == 255:
return None
if a == 0:
- return 'rgb(%d,%d,%d)'%(r,g,b)
- return 'rgba(%d,%d,%d,%f)'%(r,g,b,1.-a/255.)
+ return f'rgb({r},{g},{b})'
+ return f'rgba({r},{g},{b},{1.0 - a / 255.0:f})'
except:
return None
diff --git a/src/calibre/ebooks/lrf/lrfparser.py b/src/calibre/ebooks/lrf/lrfparser.py
index 80d9f6f7b5..7358bd4461 100644
--- a/src/calibre/ebooks/lrf/lrfparser.py
+++ b/src/calibre/ebooks/lrf/lrfparser.py
@@ -116,7 +116,7 @@ class LRFDocument(LRFMetaFile):
close = '\n'
pt_id = page_tree.id
else:
- pages += '\n'%(page_tree.id,)
+ pages += f'\n'
close = '\n'
for page in page_tree:
pages += str(page)
diff --git a/src/calibre/ebooks/lrf/objects.py b/src/calibre/ebooks/lrf/objects.py
index b527427f1f..485f5a9bc4 100644
--- a/src/calibre/ebooks/lrf/objects.py
+++ b/src/calibre/ebooks/lrf/objects.py
@@ -261,7 +261,7 @@ class Color:
return (self.r, self.g, self.b, 0xff-self.a)[i] # In Qt 0xff is opaque while in LRS 0x00 is opaque
def to_html(self):
- return 'rgb(%d, %d, %d)'%(self.r, self.g, self.b)
+ return f'rgb({self.r}, {self.g}, {self.b})'
class EmptyPageElement:
@@ -303,7 +303,7 @@ class Wait(EmptyPageElement):
self.time = time
def __str__(self):
- return '\n\n'%(self.time)
+ return f'\n\n'
class Locate(EmptyPageElement):
@@ -323,8 +323,7 @@ class BlockSpace(EmptyPageElement):
self.xspace, self.yspace = xspace, yspace
def __str__(self):
- return '\n\n'%\
- (self.xspace, self.yspace)
+ return f'\n\n'
class Page(LRFStream):
@@ -420,7 +419,7 @@ class Page(LRFStream):
yield from self.content
def __str__(self):
- s = '\n\n'%(self.style_id, self.id)
+ s = f'\n\n'
for i in self:
s += str(i)
s += '\n\n'
@@ -470,11 +469,11 @@ class BlockAttr(StyleObject, LRFObject):
margin = str(obj.sidemargin) + 'px'
ans += item('margin-left: {m}; margin-right: {m};'.format(**dict(m=margin)))
if hasattr(obj, 'topskip'):
- ans += item('margin-top: %dpx;'%obj.topskip)
+ ans += item(f'margin-top: {obj.topskip}px;')
if hasattr(obj, 'footskip'):
- ans += item('margin-bottom: %dpx;'%obj.footskip)
+ ans += item(f'margin-bottom: {obj.footskip}px;')
if hasattr(obj, 'framewidth'):
- ans += item('border: solid %dpx'%obj.framewidth)
+ ans += item(f'border: solid {obj.framewidth}px')
if hasattr(obj, 'framecolor') and obj.framecolor.a < 255:
ans += item(f'border-color: {obj.framecolor.to_html()};')
if hasattr(obj, 'bgcolor') and obj.bgcolor.a < 255:
@@ -602,9 +601,9 @@ class Block(LRFStream, TextCSS):
self.attrs[attr] = getattr(self, attr)
def __str__(self):
- s = '\n<%s objid="%d" blockstyle="%s" '%(self.name, self.id, getattr(self, 'style_id', ''))
+ s = f"\n<{self.name} objid=\"{self.id}\" blockstyle=\"{getattr(self, 'style_id', '')}\" "
if hasattr(self, 'textstyle_id'):
- s += 'textstyle="%d" '%(self.textstyle_id,)
+ s += f'textstyle="{self.textstyle_id}" '
for attr in self.attrs:
s += f'{attr}="{self.attrs[attr]}" '
if self.name != 'ImageBlock':
@@ -933,8 +932,7 @@ class Image(LRFObject):
data = property(fget=lambda self: self._document.objects[self.refstream].stream)
def __str__(self):
- return '\n'%\
- (self.id, self.x0, self.y0, self.x1, self.y1, self.xsize, self.ysize, self.refstream)
+ return f'\n'
class PutObj(EmptyPageElement):
@@ -944,7 +942,7 @@ class PutObj(EmptyPageElement):
self.object = objects[refobj]
def __str__(self):
- return ''%(self.x1, self.y1, self.refobj)
+ return f''
class Canvas(LRFStream):
diff --git a/src/calibre/ebooks/lrf/pylrs/pylrs.py b/src/calibre/ebooks/lrf/pylrs/pylrs.py
index 117f3da429..4de0ac55b6 100644
--- a/src/calibre/ebooks/lrf/pylrs/pylrs.py
+++ b/src/calibre/ebooks/lrf/pylrs/pylrs.py
@@ -341,7 +341,7 @@ class LrsObject:
if labelName is None:
labelName = name
if labelDecorate:
- label = '%s.%d' % (labelName, self.objId)
+ label = f'{labelName}.{self.objId}'
else:
label = str(self.objId)
element.attrib[objlabel] = label
diff --git a/src/calibre/ebooks/lrf/tags.py b/src/calibre/ebooks/lrf/tags.py
index 136424588e..5291702c9b 100644
--- a/src/calibre/ebooks/lrf/tags.py
+++ b/src/calibre/ebooks/lrf/tags.py
@@ -188,7 +188,7 @@ class Tag:
self.offset = stream.tell()
tag_id = struct.unpack('L', self.record0[0x54:0x58])
title_length, = unpack('>L', self.record0[0x58:0x5c])
- title_in_file, = unpack('%ds' % (title_length), self.record0[title_offset:title_offset + title_length])
+ title_in_file, = unpack(f'{title_length}s', self.record0[title_offset:title_offset + title_length])
# Adjust length to accommodate PrimaryINDX if necessary
mobi_header_length, = unpack('>L', self.record0[0x14:0x18])
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 6266d35c37..1fbe725b11 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -226,7 +226,7 @@ class ManifestItem(Resource): # {{{
return self.href()
if index == 1:
return self.media_type
- raise IndexError('%d out of bounds.'%index)
+ raise IndexError(f'{index} out of bounds.')
# }}}
@@ -237,7 +237,7 @@ class Manifest(ResourceCollection): # {{{
self.append(ManifestItem.from_opf_manifest_item(item, dir))
id = item.get('id', '')
if not id:
- id = 'id%d'%self.next_id
+ id = f'id{self.next_id}'
self[-1].id = id
self.next_id += 1
@@ -261,7 +261,7 @@ class Manifest(ResourceCollection): # {{{
mi = ManifestItem(path, is_path=True)
if mt:
mi.mime_type = mt
- mi.id = 'id%d'%m.next_id
+ mi.id = f'id{m.next_id}'
m.next_id += 1
m.append(mi)
return m
@@ -270,7 +270,7 @@ class Manifest(ResourceCollection): # {{{
mi = ManifestItem(path, is_path=True)
if mime_type:
mi.mime_type = mime_type
- mi.id = 'id%d'%self.next_id
+ mi.id = f'id{self.next_id}'
self.next_id += 1
self.append(mi)
return mi.id
@@ -787,7 +787,7 @@ class OPF: # {{{
c = 1
while manifest_id in ids:
c += 1
- manifest_id = 'id%d'%c
+ manifest_id = f'id{c}'
if not media_type:
media_type = 'application/xhtml+xml'
ans = etree.Element('{{{}}}item'.format(self.NAMESPACES['opf']),
@@ -801,7 +801,7 @@ class OPF: # {{{
def replace_manifest_item(self, item, items):
items = [self.create_manifest_item(*i) for i in items]
for i, item2 in enumerate(items):
- item2.set('id', item.get('id')+'.%d'%(i+1))
+ item2.set('id', item.get('id')+f'.{i + 1}')
manifest = item.getparent()
index = manifest.index(item)
manifest[index:index+1] = items
diff --git a/src/calibre/ebooks/metadata/pdf.py b/src/calibre/ebooks/metadata/pdf.py
index 7b04b0d570..82dfe952b5 100644
--- a/src/calibre/ebooks/metadata/pdf.py
+++ b/src/calibre/ebooks/metadata/pdf.py
@@ -39,7 +39,7 @@ def read_info(outputdir, get_cover):
try:
raw = subprocess.check_output([pdfinfo, '-enc', 'UTF-8', '-isodates', 'src.pdf'])
except subprocess.CalledProcessError as e:
- prints('pdfinfo errored out with return code: %d'%e.returncode)
+ prints(f'pdfinfo errored out with return code: {e.returncode}')
return None
try:
info_raw = raw.decode('utf-8')
@@ -63,7 +63,7 @@ def read_info(outputdir, get_cover):
try:
raw = subprocess.check_output([pdfinfo, '-meta', 'src.pdf']).strip()
except subprocess.CalledProcessError as e:
- prints('pdfinfo failed to read XML metadata with return code: %d'%e.returncode)
+ prints(f'pdfinfo failed to read XML metadata with return code: {e.returncode}')
else:
parts = re.split(br'^Metadata:', raw, 1, flags=re.MULTILINE)
if len(parts) > 1:
@@ -77,7 +77,7 @@ def read_info(outputdir, get_cover):
subprocess.check_call([pdftoppm, '-singlefile', '-jpeg', '-cropbox',
'src.pdf', 'cover'])
except subprocess.CalledProcessError as e:
- prints('pdftoppm errored out with return code: %d'%e.returncode)
+ prints(f'pdftoppm errored out with return code: {e.returncode}')
return ans
diff --git a/src/calibre/ebooks/metadata/toc.py b/src/calibre/ebooks/metadata/toc.py
index 8cfd54d093..0b164b154b 100644
--- a/src/calibre/ebooks/metadata/toc.py
+++ b/src/calibre/ebooks/metadata/toc.py
@@ -263,7 +263,7 @@ class TOC(list):
if not text:
text = ''
c[1] += 1
- item_id = 'num_%d'%c[1]
+ item_id = f'num_{c[1]}'
text = clean_xml_chars(text)
elem = E.navPoint(
E.navLabel(E.text(re.sub(r'\s+', ' ', text))),
diff --git a/src/calibre/ebooks/metadata/topaz.py b/src/calibre/ebooks/metadata/topaz.py
index 529cf2b197..9a1383cae6 100644
--- a/src/calibre/ebooks/metadata/topaz.py
+++ b/src/calibre/ebooks/metadata/topaz.py
@@ -148,7 +148,7 @@ class MetadataUpdater:
for tag in self.topaz_headers:
print(f'{tag}: ')
num_recs = len(self.topaz_headers[tag]['blocks'])
- print(' num_recs: %d' % num_recs)
+ print(f' num_recs: {num_recs}')
if num_recs:
print(' starting offset: 0x{:x}'.format(self.topaz_headers[tag]['blocks'][0]['offset']))
diff --git a/src/calibre/ebooks/metadata/utils.py b/src/calibre/ebooks/metadata/utils.py
index f45a5c6e48..35882e1417 100644
--- a/src/calibre/ebooks/metadata/utils.py
+++ b/src/calibre/ebooks/metadata/utils.py
@@ -81,7 +81,7 @@ def ensure_unique(template, existing):
c = 0
while q in existing:
c += 1
- q = '%s-%d%s' % (b, c, e)
+ q = f'{b}-{c}{e}'
return q
diff --git a/src/calibre/ebooks/metadata/xmp.py b/src/calibre/ebooks/metadata/xmp.py
index bf9f1d25cd..c52a31d03a 100644
--- a/src/calibre/ebooks/metadata/xmp.py
+++ b/src/calibre/ebooks/metadata/xmp.py
@@ -585,7 +585,7 @@ def find_nsmap(elems):
ans[pp] = ns
else:
i += 1
- ans['ns%d' % i] = ns
+ ans[f'ns{i}'] = ns
return ans
diff --git a/src/calibre/ebooks/mobi/debug/containers.py b/src/calibre/ebooks/mobi/debug/containers.py
index 3c66d69e29..6a6b58dfe4 100644
--- a/src/calibre/ebooks/mobi/debug/containers.py
+++ b/src/calibre/ebooks/mobi/debug/containers.py
@@ -46,19 +46,19 @@ class ContainerHeader:
def __str__(self):
ans = [('*'*10) + ' Container Header ' + ('*'*10)]
a = ans.append
- a('Record size: %d' % self.record_size)
- a('Type: %d' % self.type)
- a('Total number of records in this container: %d' % self.count)
+ a(f'Record size: {self.record_size}')
+ a(f'Type: {self.type}')
+ a(f'Total number of records in this container: {self.count}')
a(f'Encoding: {self.encoding}')
a(f'Unknowns1: {self.unknowns1}')
- a('Num of resource records: %d' % self.num_of_resource_records)
- a('Num of non-dummy resource records: %d' % self.num_of_non_dummy_resource_records)
- a('Offset to href record: %d' % self.offset_to_href_record)
+ a(f'Num of resource records: {self.num_of_resource_records}')
+ a(f'Num of non-dummy resource records: {self.num_of_non_dummy_resource_records}')
+ a(f'Offset to href record: {self.offset_to_href_record}')
a(f'Unknowns2: {self.unknowns2}')
- a('Header length: %d' % self.header_length)
+ a(f'Header length: {self.header_length}')
a(f'Title Length: {self.title_length}')
a(f'hrefs: {self.hrefs}')
- a('Null bytes after EXTH: %d' % self.null_bytes_after_exth)
+ a(f'Null bytes after EXTH: {self.null_bytes_after_exth}')
if len(self.bytes_after_exth) != self.null_bytes_after_exth:
a('Non-null bytes present after EXTH header!!!!')
return '\n'.join(ans) + '\n\n' + str(self.exth) + '\n\n' + (f'Title: {self.title}')
diff --git a/src/calibre/ebooks/mobi/debug/headers.py b/src/calibre/ebooks/mobi/debug/headers.py
index c97c64264b..041c04d64b 100644
--- a/src/calibre/ebooks/mobi/debug/headers.py
+++ b/src/calibre/ebooks/mobi/debug/headers.py
@@ -116,8 +116,7 @@ class Record: # {{{
@property
def header(self):
- return 'Offset: %d Flags: %d UID: %d First 4 bytes: %r Size: %d'%(self.offset, self.flags,
- self.uid, self.raw[:4], len(self.raw))
+ return f'Offset: {self.offset} Flags: {self.flags} UID: {self.uid} First 4 bytes: {self.raw[:4]!r} Size: {len(self.raw)}'
# }}}
@@ -213,7 +212,7 @@ class EXTHRecord:
self.data = binascii.hexlify(self.data)
def __str__(self):
- return '%s (%d): %r'%(self.name, self.type, self.data)
+ return f'{self.name} ({self.type}): {self.data!r}'
class EXTHHeader:
@@ -254,8 +253,8 @@ class EXTHHeader:
def __str__(self):
ans = ['*'*20 + ' EXTH Header '+ '*'*20]
- ans.append('EXTH header length: %d'%self.length)
- ans.append('Number of EXTH records: %d'%self.count)
+ ans.append(f'EXTH header length: {self.length}')
+ ans.append(f'Number of EXTH records: {self.count}')
ans.append('EXTH records...')
for r in self.records:
ans.append(str(r))
@@ -416,7 +415,7 @@ class MOBIHeader: # {{{
self.last_resource_record = self.exth.kf8_header_index - 2
def __str__(self):
- ans = ['*'*20 + ' MOBI %d Header '%self.file_version+ '*'*20]
+ ans = ['*'*20 + f' MOBI {self.file_version} Header '+ '*'*20]
a = ans.append
@@ -427,39 +426,39 @@ class MOBIHeader: # {{{
def r(d, attr):
x = getattr(self, attr)
if attr in self.relative_records and x != NULL_INDEX:
- a('%s: Absolute: %d Relative: %d'%(d, x, x-self.header_offset))
+ a(f'{d}: Absolute: {x} Relative: {x - self.header_offset}')
else:
i(d, x)
a(f'Compression: {self.compression}')
a(f'Unused: {self.unused!r}')
- a('Text length: %d'%self.text_length)
- a('Number of text records: %d'%self.number_of_text_records)
- a('Text record size: %d'%self.text_record_size)
+ a(f'Text length: {self.text_length}')
+ a(f'Number of text records: {self.number_of_text_records}')
+ a(f'Text record size: {self.text_record_size}')
a(f'Encryption: {self.encryption_type}')
a(f'Unknown: {self.unknown!r}')
a(f'Identifier: {self.identifier!r}')
- a('Header length: %d'% self.length)
+ a(f'Header length: {self.length}')
a(f'Type: {self.type}')
a(f'Encoding: {self.encoding}')
a(f'UID: {self.uid!r}')
- a('File version: %d'%self.file_version)
+ a(f'File version: {self.file_version}')
r('Meta Orth Index', 'meta_orth_indx')
r('Meta Infl Index', 'meta_infl_indx')
r('Secondary index record', 'secondary_index_record')
a(f'Reserved: {self.reserved!r}')
r('First non-book record', 'first_non_book_record')
- a('Full name offset: %d'%self.fullname_offset)
- a('Full name length: %d bytes'%self.fullname_length)
+ a(f'Full name offset: {self.fullname_offset}')
+ a(f'Full name length: {self.fullname_length} bytes')
a(f'Langcode: {self.locale_raw!r}')
a(f'Language: {self.language}')
a(f'Sub language: {self.sublanguage}')
a(f'Input language: {self.input_language!r}')
a(f'Output language: {self.output_langauage!r}')
- a('Min version: %d'%self.min_version)
+ a(f'Min version: {self.min_version}')
r('First Image index', 'first_image_index')
r('Huffman record offset', 'huffman_record_offset')
- a('Huffman record count: %d'%self.huffman_record_count)
+ a(f'Huffman record count: {self.huffman_record_count}')
r('Huffman table offset', 'datp_record_offset')
a(f'Huffman table length: {self.datp_record_count!r}')
a(f'EXTH flags: {bin(self.exth_flags)[2:]} ({self.has_exth})')
@@ -472,18 +471,18 @@ class MOBIHeader: # {{{
if self.has_extra_data_flags:
a(f'Unknown4: {self.unknown4!r}')
if hasattr(self, 'first_text_record'):
- a('First content record: %d'%self.first_text_record)
- a('Last content record: %d'%self.last_text_record)
+ a(f'First content record: {self.first_text_record}')
+ a(f'Last content record: {self.last_text_record}')
else:
r('FDST Index', 'fdst_idx')
- a('FDST Count: %d'% self.fdst_count)
+ a(f'FDST Count: {self.fdst_count}')
r('FCIS number', 'fcis_number')
- a('FCIS count: %d'% self.fcis_count)
+ a(f'FCIS count: {self.fcis_count}')
r('FLIS number', 'flis_number')
- a('FLIS count: %d'% self.flis_count)
+ a(f'FLIS count: {self.flis_count}')
a(f'Unknown6: {self.unknown6!r}')
r('SRCS record index', 'srcs_record_index')
- a('Number of SRCS records?: %d'%self.num_srcs_records)
+ a(f'Number of SRCS records?: {self.num_srcs_records}')
a(f'Unknown7: {self.unknown7!r}')
a(f'Extra data flags: {bin(self.extra_data_flags)} (has multibyte: {self.has_multibytes}) '
f'(has indexing: {self.has_indexing_bytes}) (has uncrossable breaks: {self.has_uncrossable_breaks})')
@@ -502,8 +501,7 @@ class MOBIHeader: # {{{
ans += '\n\n' + str(self.exth)
ans += f'\n\nBytes after EXTH ({len(self.bytes_after_exth)} bytes): {format_bytes(self.bytes_after_exth)}'
- ans += '\nNumber of bytes after full name: %d' % (len(self.raw) - (self.fullname_offset +
- self.fullname_length))
+ ans += f'\nNumber of bytes after full name: {len(self.raw) - (self.fullname_offset + self.fullname_length)}'
ans += f'\nRecord 0 length: {len(self.raw)}'
return ans
@@ -599,13 +597,12 @@ class TextRecord: # {{{
for typ, val in iteritems(self.trailing_data):
if isinstance(typ, numbers.Integral):
- print('Record %d has unknown trailing data of type: %d : %r'%
- (idx, typ, val))
+ print(f'Record {idx} has unknown trailing data of type: {typ} : {val!r}')
self.idx = idx
def dump(self, folder):
- name = '%06d'%self.idx
+ name = f'{self.idx:06}'
with open(os.path.join(folder, name+'.txt'), 'wb') as f:
f.write(self.raw)
with open(os.path.join(folder, name+'.trailing_data'), 'wb') as f:
diff --git a/src/calibre/ebooks/mobi/debug/index.py b/src/calibre/ebooks/mobi/debug/index.py
index 6b62cb86f5..5fecd43dca 100644
--- a/src/calibre/ebooks/mobi/debug/index.py
+++ b/src/calibre/ebooks/mobi/debug/index.py
@@ -100,14 +100,14 @@ class Index:
ans.extend(['', ''])
ans += ['*'*10 + f' Index Record Headers ({len(self.index_headers)} records) ' + '*'*10]
for i, header in enumerate(self.index_headers):
- ans += ['*'*10 + ' Index Record %d ' % i + '*'*10]
+ ans += ['*'*10 + f' Index Record {i} ' + '*'*10]
for field in INDEX_HEADER_FIELDS:
a('%-12s: %r'%(FIELD_NAMES.get(field, field), header[field]))
if self.cncx:
a('*'*10 + ' CNCX ' + '*'*10)
for offset, val in iteritems(self.cncx):
- a('%10s: %s'%(offset, val))
+ a(f'{offset:10}: {val}')
ans.extend(['', ''])
if self.table is not None:
diff --git a/src/calibre/ebooks/mobi/debug/mobi6.py b/src/calibre/ebooks/mobi/debug/mobi6.py
index 29a6a9b014..3abe56b3e7 100644
--- a/src/calibre/ebooks/mobi/debug/mobi6.py
+++ b/src/calibre/ebooks/mobi/debug/mobi6.py
@@ -30,8 +30,7 @@ class TagX: # {{{
self.is_eof = (self.eof == 1 and self.tag == 0 and self.num_values == 0 and self.bitmask == 0)
def __repr__(self):
- return 'TAGX(tag=%02d, num_values=%d, bitmask=%r, eof=%d)' % (self.tag,
- self.num_values, bin(self.bitmask), self.eof)
+ return f'TAGX(tag={self.tag:02}, num_values={self.num_values}, bitmask={bin(self.bitmask)!r}, eof={self.eof})'
# }}}
@@ -55,7 +54,7 @@ class SecondaryIndexHeader: # {{{
'cp1252'}.get(self.index_encoding_num, 'unknown')
if self.index_encoding == 'unknown':
raise ValueError(
- 'Unknown index encoding: %d'%self.index_encoding_num)
+ f'Unknown index encoding: {self.index_encoding_num}')
self.unknown2 = raw[32:36]
self.num_index_entries, = struct.unpack('>I', raw[36:40])
self.ordt_start, = struct.unpack('>I', raw[40:44])
@@ -102,30 +101,29 @@ class SecondaryIndexHeader: # {{{
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
len(w), not bool(w.replace(b'\0', b''))))
- a('Header length: %d'%self.header_length)
+ a(f'Header length: {self.header_length}')
u(self.unknown1)
- a('Index Type: %s (%d)'%(self.index_type_desc, self.index_type))
- a('Offset to IDXT start: %d'%self.idxt_start)
- a('Number of index records: %d'%self.index_count)
- a('Index encoding: %s (%d)'%(self.index_encoding,
- self.index_encoding_num))
+ a(f'Index Type: {self.index_type_desc} ({self.index_type})')
+ a(f'Offset to IDXT start: {self.idxt_start}')
+ a(f'Number of index records: {self.index_count}')
+ a(f'Index encoding: {self.index_encoding} ({self.index_encoding_num})')
u(self.unknown2)
- a('Number of index entries: %d'% self.num_index_entries)
- a('ORDT start: %d'%self.ordt_start)
- a('LIGT start: %d'%self.ligt_start)
- a('Number of LIGT entries: %d'%self.num_of_ligt_entries)
- a('Number of cncx blocks: %d'%self.num_of_cncx_blocks)
+ a(f'Number of index entries: {self.num_index_entries}')
+ a(f'ORDT start: {self.ordt_start}')
+ a(f'LIGT start: {self.ligt_start}')
+ a(f'Number of LIGT entries: {self.num_of_ligt_entries}')
+ a(f'Number of cncx blocks: {self.num_of_cncx_blocks}')
u(self.unknown3)
- a('TAGX offset: %d'%self.tagx_offset)
+ a(f'TAGX offset: {self.tagx_offset}')
u(self.unknown4)
a('\n\n')
- a('*'*20 + ' TAGX Header (%d bytes)'%self.tagx_header_length+ '*'*20)
- a('Header length: %d'%self.tagx_header_length)
- a('Control byte count: %d'%self.tagx_control_byte_count)
+ a('*'*20 + f' TAGX Header ({self.tagx_header_length} bytes)'+ '*'*20)
+ a(f'Header length: {self.tagx_header_length}')
+ a(f'Control byte count: {self.tagx_control_byte_count}')
for i in self.tagx_entries:
a('\t' + repr(i))
a(f'Index of last IndexEntry in secondary index record: {self.last_entry}')
- a('Number of entries in the NCX: %d'% self.ncx_count)
+ a(f'Number of entries in the NCX: {self.ncx_count}')
return '\n'.join(ans)
@@ -154,7 +152,7 @@ class IndexHeader: # {{{
'cp1252'}.get(self.index_encoding_num, 'unknown')
if self.index_encoding == 'unknown':
raise ValueError(
- 'Unknown index encoding: %d'%self.index_encoding_num)
+ f'Unknown index encoding: {self.index_encoding_num}')
self.possibly_language = raw[32:36]
self.num_index_entries, = struct.unpack('>I', raw[36:40])
self.ordt_start, = struct.unpack('>I', raw[40:44])
@@ -204,31 +202,30 @@ class IndexHeader: # {{{
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
len(w), not bool(w.replace(b'\0', b''))))
- a('Header length: %d'%self.header_length)
+ a(f'Header length: {self.header_length}')
u(self.unknown1)
- a('Header type: %d'%self.header_type)
- a('Index Type: %s (%d)'%(self.index_type_desc, self.index_type))
- a('Offset to IDXT start: %d'%self.idxt_start)
- a('Number of index records: %d'%self.index_count)
- a('Index encoding: %s (%d)'%(self.index_encoding,
- self.index_encoding_num))
+ a(f'Header type: {self.header_type}')
+ a(f'Index Type: {self.index_type_desc} ({self.index_type})')
+ a(f'Offset to IDXT start: {self.idxt_start}')
+ a(f'Number of index records: {self.index_count}')
+ a(f'Index encoding: {self.index_encoding} ({self.index_encoding_num})')
a(f'Unknown (possibly language?): {self.possibly_language!r}')
- a('Number of index entries: %d'% self.num_index_entries)
- a('ORDT start: %d'%self.ordt_start)
- a('LIGT start: %d'%self.ligt_start)
- a('Number of LIGT entries: %d'%self.num_of_ligt_entries)
- a('Number of cncx blocks: %d'%self.num_of_cncx_blocks)
+ a(f'Number of index entries: {self.num_index_entries}')
+ a(f'ORDT start: {self.ordt_start}')
+ a(f'LIGT start: {self.ligt_start}')
+ a(f'Number of LIGT entries: {self.num_of_ligt_entries}')
+ a(f'Number of cncx blocks: {self.num_of_cncx_blocks}')
u(self.unknown2)
- a('TAGX offset: %d'%self.tagx_offset)
+ a(f'TAGX offset: {self.tagx_offset}')
u(self.unknown3)
a('\n\n')
- a('*'*20 + ' TAGX Header (%d bytes)'%self.tagx_header_length+ '*'*20)
- a('Header length: %d'%self.tagx_header_length)
- a('Control byte count: %d'%self.tagx_control_byte_count)
+ a('*'*20 + f' TAGX Header ({self.tagx_header_length} bytes)'+ '*'*20)
+ a(f'Header length: {self.tagx_header_length}')
+ a(f'Control byte count: {self.tagx_control_byte_count}')
for i in self.tagx_entries:
a('\t' + repr(i))
a(f'Index of last IndexEntry in primary index record: {self.last_entry}')
- a('Number of entries in the NCX: %d'% self.ncx_count)
+ a(f'Number of entries in the NCX: {self.ncx_count}')
return '\n'.join(ans)
# }}}
@@ -275,7 +272,7 @@ class Tag: # {{{
self.attr, self.desc = self.TAG_MAP[tag_type]
else:
print('Unknown tag value: %s')
- self.desc = '??Unknown (tag value: %d)'%tag_type
+ self.desc = f'??Unknown (tag value: {tag_type})'
self.attr = 'unknown'
if '_offset' in self.attr:
@@ -368,8 +365,7 @@ class IndexEntry: # {{{
if tag.value is not None:
ans.append('\t'+str(tag))
if self.first_child_index != -1:
- ans.append('\tNumber of children: %d'%(self.last_child_index -
- self.first_child_index + 1))
+ ans.append(f'\tNumber of children: {self.last_child_index - self.first_child_index + 1}')
return '\n'.join(ans)
# }}}
@@ -458,8 +454,7 @@ class CNCX: # {{{
except:
byts = raw[pos:]
r = format_bytes(byts)
- print('CNCX entry at offset %d has unknown format %s'%(
- pos+record_offset, r))
+ print(f'CNCX entry at offset {pos + record_offset} has unknown format {r}')
self.records[pos+record_offset] = r
pos = len(raw)
pos += consumed+length
@@ -471,7 +466,7 @@ class CNCX: # {{{
def __str__(self):
ans = ['*'*20 + f' cncx ({len(self.records)} strings) '+ '*'*20]
for k, v in iteritems(self.records):
- ans.append('%10d : %s'%(k, v))
+ ans.append(f'{k:10} : {v}')
return '\n'.join(ans)
# }}}
@@ -485,7 +480,7 @@ class ImageRecord: # {{{
self.idx = idx
def dump(self, folder):
- name = '%06d'%self.idx
+ name = f'{self.idx:06}'
with open(os.path.join(folder, name+'.'+self.fmt), 'wb') as f:
f.write(self.raw)
@@ -497,7 +492,7 @@ class BinaryRecord: # {{{
def __init__(self, idx, record):
self.raw = record.raw
sig = self.raw[:4]
- name = '%06d'%idx
+ name = f'{idx:06}'
if sig in {b'FCIS', b'FLIS', b'SRCS', b'DATP', b'RESC', b'BOUN',
b'FDST', b'AUDI', b'VIDE', b'CRES', b'CONT', b'CMET'}:
name += '-' + sig.decode('ascii')
@@ -516,7 +511,7 @@ class FontRecord: # {{{
def __init__(self, idx, record):
self.raw = record.raw
- name = '%06d'%idx
+ name = f'{idx:06}'
self.font = read_font_record(self.raw)
if self.font['err']:
raise ValueError('Failed to read font record: {} Headers: {}'.format(
@@ -564,7 +559,7 @@ class TBSIndexing: # {{{
for i in self.indices:
if i.index in {idx, str(idx)}:
return i
- raise IndexError('Index %d not found'%idx)
+ raise IndexError(f'Index {idx} not found')
def __str__(self):
ans = ['*'*20 + f' TBS Indexing ({len(self.record_indices)} records) '+ '*'*20]
@@ -580,13 +575,12 @@ class TBSIndexing: # {{{
continue
types[tbs_type] += strings
for typ, strings in iteritems(types):
- with open(os.path.join(bdir, 'tbs_type_%d.txt'%typ), 'wb') as f:
+ with open(os.path.join(bdir, f'tbs_type_{typ}.txt'), 'wb') as f:
f.write(as_bytes('\n'.join(strings)))
def dump_record(self, r, dat):
ans = []
- ans.append('\nRecord #%d: Starts at: %d Ends at: %d'%(r.idx,
- dat['geom'][0], dat['geom'][1]))
+ ans.append(f"\nRecord #{r.idx}: Starts at: {dat['geom'][0]} Ends at: {dat['geom'][1]}")
s, e, c = dat['starts'], dat['ends'], dat['complete']
ans.append(('\tContains: %d index entries '
'(%d ends, %d complete, %d starts)')%tuple(map(len, (s+e+c, e,
@@ -597,9 +591,7 @@ class TBSIndexing: # {{{
if entries:
ans.append(f'\t{typ}:')
for x in entries:
- ans.append(('\t\tIndex Entry: %s (Parent index: %s, '
- 'Depth: %d, Offset: %d, Size: %d) [%s]')%(
- x.index, x.parent_index, x.depth, x.offset, x.size, x.label))
+ ans.append(f"\t\tIndex Entry: {x.index} (Parent index: {x.parent_index}, Depth: {x.depth}, Offset: {x.offset}, Size: {x.size}) [{x.label}]")
def bin4(num):
ans = bin(num)[2:]
@@ -615,8 +607,8 @@ class TBSIndexing: # {{{
byts = byts[consumed:]
for k in extra:
tbs_type |= k
- ans.append('\nTBS: %d (%s)'%(tbs_type, bin4(tbs_type)))
- ans.append('Outermost index: %d'%outermost_index)
+ ans.append(f'\nTBS: {tbs_type} ({bin4(tbs_type)})')
+ ans.append(f'Outermost index: {outermost_index}')
ans.append(f'Unknown extra start bytes: {repr_extra(extra)}')
if is_periodical: # Hierarchical periodical
try:
@@ -626,7 +618,7 @@ class TBSIndexing: # {{{
import traceback
traceback.print_exc()
a = []
- print('Failed to decode TBS bytes for record: %d'%r.idx)
+ print(f'Failed to decode TBS bytes for record: {r.idx}')
ans += a
if byts:
sbyts = tuple(hex(b)[2:] for b in byts)
@@ -654,35 +646,25 @@ class TBSIndexing: # {{{
raise ValueError('Dont know how to interpret flags'
f' {extra!r} while reading section transitions')
nsi = self.get_index(psi.index+1)
- ans.append('Last article in this record of section %d'
- ' (relative to next section index [%d]): '
- '%d [%d absolute index]'%(psi.index, nsi.index, ai,
- ai+nsi.index))
+ ans.append(f'Last article in this record of section {psi.index} (relative to next section index [{nsi.index}]): {ai} [{ai + nsi.index} absolute index]')
psi = nsi
continue
- ans.append('First article in this record of section %d'
- ' (relative to its parent section): '
- '%d [%d absolute index]'%(psi.index, ai, ai+psi.index))
+ ans.append(f'First article in this record of section {psi.index} (relative to its parent section): {ai} [{ai + psi.index} absolute index]')
num = extra.get(0b0100, None)
if num is None:
- msg = ('The section %d has at most one article'
- ' in this record')%psi.index
+ msg = f"The section {psi.index} has at most one article in this record"
else:
- msg = ('Number of articles in this record of '
- 'section %d: %d')%(psi.index, num)
+ msg = f"Number of articles in this record of section {psi.index}: {num}"
ans.append(msg)
offset = extra.get(0b0001, None)
if offset is not None:
if offset == 0:
- ans.append('This record is spanned by the article:'
- '%d'%(ai+psi.index))
+ ans.append(f'This record is spanned by the article:{ai + psi.index}')
else:
- ans.append('->Offset to start of next section (%d) from start'
- ' of record: %d [%d absolute offset]'%(psi.index+1,
- offset, offset+record_offset))
+ ans.append(f'->Offset to start of next section ({psi.index + 1}) from start of record: {offset} [{offset + record_offset} absolute offset]')
return byts
# }}}
@@ -698,8 +680,7 @@ class TBSIndexing: # {{{
f' {si.index}')
if 0b0100 in extra:
num = extra[0b0100]
- ans.append('The number of articles from the section %d'
- ' in this record: %s'%(si.index, num))
+ ans.append(f'The number of articles from the section {si.index} in this record: {num}')
elif 0b0001 in extra:
eof = extra[0b0001]
if eof != 0:
@@ -791,7 +772,7 @@ class MOBIFile: # {{{
p()
p('Record headers:')
for i, r in enumerate(self.records):
- p('%6d. %s'%(i, r.header))
+ p(f'{i:6}. {r.header}')
p()
p(str(self.mobi_header))
diff --git a/src/calibre/ebooks/mobi/debug/mobi8.py b/src/calibre/ebooks/mobi/debug/mobi8.py
index 0053c1705d..f7b20a0296 100644
--- a/src/calibre/ebooks/mobi/debug/mobi8.py
+++ b/src/calibre/ebooks/mobi/debug/mobi8.py
@@ -53,7 +53,7 @@ class FDST:
class File:
def __init__(self, skel, skeleton, text, first_aid, sections):
- self.name = 'part%04d'%skel.file_number
+ self.name = f'part{skel.file_number:04}'
self.skeleton, self.text, self.first_aid = skeleton, text, first_aid
self.sections = sections
@@ -66,7 +66,7 @@ class File:
with open('skeleton.html', 'wb') as f:
f.write(self.skeleton)
for i, text in enumerate(self.sections):
- with open('sect-%04d.html'%i, 'wb') as f:
+ with open(f'sect-{i:04}.html', 'wb') as f:
f.write(text)
@@ -101,7 +101,7 @@ class MOBIFile:
p()
p('Record headers:')
for i, r in enumerate(self.mf.records):
- p('%6d. %s'%(i, r.header))
+ p(f'{i:6}. {r.header}')
p()
p(str(self.mf.mobi8_header))
@@ -151,7 +151,7 @@ class MOBIFile:
for i, x in enumerate(boundaries):
start, end = x
raw = self.raw_text[start:end]
- with open(os.path.join(ddir, 'flow%04d.txt'%i), 'wb') as f:
+ with open(os.path.join(ddir, f'flow{i:04}.txt'), 'wb') as f:
f.write(raw)
def extract_resources(self, records):
@@ -221,7 +221,7 @@ class MOBIFile:
elif sig in known_types:
suffix = '-' + sig.decode('ascii')
- self.resource_map.append(('%s/%06d%s.%s'%(prefix, resource_index, suffix, ext),
+ self.resource_map.append((f'{prefix}/{resource_index:06}{suffix}.{ext}',
payload))
def read_tbs(self):
@@ -260,9 +260,9 @@ class MOBIFile:
for i, strands in enumerate(indexing_data):
rec = self.text_records[i]
tbs_bytes = rec.trailing_data.get('indexing', b'')
- desc = ['Record #%d'%i]
+ desc = [f'Record #{i}']
for s, strand in enumerate(strands):
- desc.append('Strand %d'%s)
+ desc.append(f'Strand {s}')
for entries in itervalues(strand):
for e in entries:
desc.append(
@@ -284,7 +284,7 @@ class MOBIFile:
extra = {bin(k):v for k, v in iteritems(extra)}
sequences.append((val, extra))
for j, seq in enumerate(sequences):
- desc.append('Sequence #%d: %r %r'%(j, seq[0], seq[1]))
+ desc.append(f'Sequence #{j}: {seq[0]!r} {seq[1]!r}')
if tbs_bytes:
desc.append(f'Remaining bytes: {format_bytes(tbs_bytes)}')
calculated_sequences = encode_strands_as_sequences(strands,
@@ -294,7 +294,7 @@ class MOBIFile:
except:
calculated_bytes = b'failed to calculate tbs bytes'
if calculated_bytes != otbs:
- print('WARNING: TBS mismatch for record %d'%i)
+ print(f'WARNING: TBS mismatch for record {i}')
desc.append('WARNING: TBS mismatch!')
desc.append(f'Calculated sequences: {calculated_sequences!r}')
desc.append('')
@@ -321,7 +321,7 @@ def inspect_mobi(mobi_file, ddir):
fo.write(payload)
for i, container in enumerate(f.containers):
- with open(os.path.join(ddir, 'container%d.txt' % (i + 1)), 'wb') as cf:
+ with open(os.path.join(ddir, f'container{i + 1}.txt'), 'wb') as cf:
cf.write(str(container).encode('utf-8'))
if f.fdst:
diff --git a/src/calibre/ebooks/mobi/reader/headers.py b/src/calibre/ebooks/mobi/reader/headers.py
index 6d8bd40d85..2c61f30849 100644
--- a/src/calibre/ebooks/mobi/reader/headers.py
+++ b/src/calibre/ebooks/mobi/reader/headers.py
@@ -220,8 +220,7 @@ class BookHeader:
}[self.codepage]
except (IndexError, KeyError):
self.codec = 'cp1252' if not user_encoding else user_encoding
- log.warn('Unknown codepage %d. Assuming %s' % (self.codepage,
- self.codec))
+ log.warn(f'Unknown codepage {self.codepage}. Assuming {self.codec}')
# Some KF8 files have header length == 264 (generated by kindlegen
# 2.9?). See https://bugs.launchpad.net/bugs/1179144
max_header_length = 500 # We choose 500 for future versions of kindlegen
diff --git a/src/calibre/ebooks/mobi/reader/index.py b/src/calibre/ebooks/mobi/reader/index.py
index ad6e13dc73..f70ee76022 100644
--- a/src/calibre/ebooks/mobi/reader/index.py
+++ b/src/calibre/ebooks/mobi/reader/index.py
@@ -16,7 +16,7 @@ PTagX = namedtuple('PTagX', 'tag value_count value_bytes num_of_values')
INDEX_HEADER_FIELDS = (
'len', 'nul1', 'type', 'gen', 'start', 'count', 'code',
'lng', 'total', 'ordt', 'ligt', 'nligt', 'ncncx'
- ) + tuple('unknown%d'%i for i in range(27)) + ('ocnt', 'oentries',
+ ) + tuple(f'unknown{i}' for i in range(27)) + ('ocnt', 'oentries',
'ordt1', 'ordt2', 'tagx')
@@ -47,7 +47,7 @@ def parse_indx_header(data):
check_signature(data, b'INDX')
words = INDEX_HEADER_FIELDS
num = len(words)
- values = struct.unpack('>%dL' % num, data[4:4*(num+1)])
+ values = struct.unpack(f'>{num}L', data[4:4*(num+1)])
ans = dict(zip(words, values))
ans['idx_header_end_pos'] = 4 * (num+1)
ordt1, ordt2 = ans['ordt1'], ans['ordt2']
@@ -103,8 +103,7 @@ class CNCX: # {{{
except:
byts = raw[pos:]
r = format_bytes(byts)
- print('CNCX entry at offset %d has unknown format %s'%(
- pos+record_offset, r))
+ print(f'CNCX entry at offset {pos + record_offset} has unknown format {r}')
self.records[pos+record_offset] = r
pos = len(raw)
pos += consumed+length
diff --git a/src/calibre/ebooks/mobi/reader/mobi6.py b/src/calibre/ebooks/mobi/reader/mobi6.py
index 085fc2f42f..aac14c1bd1 100644
--- a/src/calibre/ebooks/mobi/reader/mobi6.py
+++ b/src/calibre/ebooks/mobi/reader/mobi6.py
@@ -525,7 +525,7 @@ class MobiReader:
except Exception:
pass
else:
- attrib['src'] = 'images/' + image_name_map.get(recindex, '%05d.jpg' % recindex)
+ attrib['src'] = 'images/' + image_name_map.get(recindex, f'{recindex:05}.jpg')
for attr in ('width', 'height'):
if attr in attrib:
val = attrib[attr]
@@ -577,7 +577,7 @@ class MobiReader:
ncls = sel
break
if ncls is None:
- ncls = 'calibre_%d' % i
+ ncls = f'calibre_{i}'
self.tag_css_rules[ncls] = rule
cls = attrib.get('class', '')
cls = cls + (' ' if cls else '') + ncls
@@ -658,7 +658,7 @@ class MobiReader:
mi = MetaInformation(self.book_header.title, [_('Unknown')])
opf = OPFCreator(os.path.dirname(htmlfile), mi)
if hasattr(self.book_header.exth, 'cover_offset'):
- opf.cover = 'images/%05d.jpg' % (self.book_header.exth.cover_offset + 1)
+ opf.cover = f'images/{self.book_header.exth.cover_offset + 1:05}.jpg'
elif mi.cover is not None:
opf.cover = mi.cover
else:
@@ -920,7 +920,7 @@ class MobiReader:
except OSError:
self.log.warn(f'Ignoring undecodeable GIF image at index {image_index}')
continue
- path = os.path.join(output_dir, '%05d.%s' % (image_index, imgfmt))
+ path = os.path.join(output_dir, f'{image_index:05}.{imgfmt}')
image_name_map[image_index] = os.path.basename(path)
if imgfmt == 'png':
with open(path, 'wb') as f:
diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py
index dfdfe1b807..db0b943aae 100644
--- a/src/calibre/ebooks/mobi/reader/mobi8.py
+++ b/src/calibre/ebooks/mobi/reader/mobi8.py
@@ -200,7 +200,7 @@ class Mobi8Reader:
self.elems[divptr]
if i == 0:
aidtext = idtext[12:-2]
- filename = 'part%04d.html' % filenum
+ filename = f'part{filenum:04}.html'
part = text[baseptr:baseptr + length]
insertpos = insertpos - skelpos
head = skeleton[:insertpos]
@@ -256,7 +256,7 @@ class Mobi8Reader:
image_tag_pattern = re.compile(br'''(<(?:svg:)?image[^>]*>)''', re.IGNORECASE)
for j in range(1, len(self.flows)):
flowpart = self.flows[j]
- nstr = '%04d' % j
+ nstr = f'{j:04}'
m = svg_tag_pattern.search(flowpart)
if m is not None:
# svg
@@ -320,7 +320,7 @@ class Mobi8Reader:
# pos
fi = self.get_file_info(pos)
if fi.num is None and fi.start is None:
- raise ValueError('No file contains pos: %d'%pos)
+ raise ValueError(f'No file contains pos: {pos}')
textblock = self.parts[fi.num]
npos = pos - fi.start
pgt = textblock.find(b'>', npos)
@@ -391,7 +391,7 @@ class Mobi8Reader:
pos = entry['pos']
fi = self.get_file_info(pos)
if fi.filename is None:
- raise ValueError('Index entry has invalid pos: %d'%pos)
+ raise ValueError(f'Index entry has invalid pos: {pos}')
idtag = self.get_id_tag(pos)
href = f'{fi.type}/{fi.filename}'
else:
@@ -429,10 +429,9 @@ class Mobi8Reader:
pass # Ignore these records
elif typ == b'FONT':
font = read_font_record(data)
- href = 'fonts/%05d.%s' % (fname_idx, font['ext'])
+ href = f"fonts/{fname_idx:05}.{font['ext']}"
if font['err']:
- self.log.warn('Reading font record %d failed: %s'%(
- fname_idx, font['err']))
+ self.log.warn(f"Reading font record {fname_idx} failed: {font['err']}")
if font['headers']:
self.log.debug('Font record headers: {}'.format(font['headers']))
with open(href.replace('/', os.sep), 'wb') as f:
@@ -448,7 +447,7 @@ class Mobi8Reader:
elif typ == b'CRES':
data, imgtype = container.load_image(data)
if data is not None:
- href = 'images/%05d.%s'%(container.resource_index, imgtype)
+ href = f'images/{container.resource_index:05}.{imgtype}'
with open(href.replace('/', os.sep), 'wb') as f:
f.write(data)
elif typ == b'\xa0\xa0\xa0\xa0' and len(data) == 4 and container is not None:
@@ -456,7 +455,7 @@ class Mobi8Reader:
elif container is None:
if not (len(data) == len(PLACEHOLDER_GIF) and data == PLACEHOLDER_GIF):
imgtype = find_imgtype(data)
- href = 'images/%05d.%s'%(fname_idx, imgtype)
+ href = f'images/{fname_idx:05}.{imgtype}'
with open(href.replace('/', os.sep), 'wb') as f:
f.write(data)
diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py
index aed4ccaf2c..e4a93ae9b3 100644
--- a/src/calibre/ebooks/mobi/utils.py
+++ b/src/calibre/ebooks/mobi/utils.py
@@ -156,8 +156,7 @@ def test_decint(num):
raw = encint(num, forward=d)
sz = len(raw)
if (num, sz) != decint(raw, forward=d):
- raise ValueError('Failed for num %d, forward=%r: %r != %r' % (
- num, d, (num, sz), decint(raw, forward=d)))
+ raise ValueError(f'Failed for num {num}, forward={d!r}: {num, sz!r} != {decint(raw, forward=d)!r}')
def rescale_image(data, maxsizeb=IMAGE_MAX_SIZE, dimen=None):
diff --git a/src/calibre/ebooks/mobi/writer2/serializer.py b/src/calibre/ebooks/mobi/writer2/serializer.py
index a568db9a0e..3025f0f127 100644
--- a/src/calibre/ebooks/mobi/writer2/serializer.py
+++ b/src/calibre/ebooks/mobi/writer2/serializer.py
@@ -390,4 +390,4 @@ class Serializer:
self.start_offset = ioff
for hoff in hoffs:
buf.seek(hoff)
- buf.write(('%010d' % ioff).encode('utf-8'))
+ buf.write(f'{ioff:010}'.encode('utf-8'))
diff --git a/src/calibre/ebooks/mobi/writer8/index.py b/src/calibre/ebooks/mobi/writer8/index.py
index 019e76f28d..b09e3f91ad 100644
--- a/src/calibre/ebooks/mobi/writer8/index.py
+++ b/src/calibre/ebooks/mobi/writer8/index.py
@@ -267,7 +267,7 @@ class ChunkIndex(Index):
self.cncx = CNCX(c.selector for c in chunk_table)
self.entries = [
- ('%010d'%c.insert_pos, {
+ (f'{c.insert_pos:010}', {
'cncx_offset':self.cncx[c.selector],
'file_number':c.file_number,
@@ -378,7 +378,7 @@ if __name__ == '__main__':
import os
import subprocess
os.chdir('/t')
- paras = ['%d
' % i for i in range(4000)]
+ paras = [f'{i}
' for i in range(4000)]
raw = '' + '\n\n'.join(paras) + ''
src = 'index.html'
diff --git a/src/calibre/ebooks/mobi/writer8/main.py b/src/calibre/ebooks/mobi/writer8/main.py
index 134f4c9c06..7765abce14 100644
--- a/src/calibre/ebooks/mobi/writer8/main.py
+++ b/src/calibre/ebooks/mobi/writer8/main.py
@@ -302,7 +302,7 @@ class KF8Writer:
# https://bugs.launchpad.net/bugs/1489495
if id_:
cid += 1
- val = 'c%d' % cid
+ val = f'c{cid}'
self.id_map[(item.href, id_)] = val
tag.set('cid', val)
else:
diff --git a/src/calibre/ebooks/mobi/writer8/skeleton.py b/src/calibre/ebooks/mobi/writer8/skeleton.py
index aaf2584304..8252ae3908 100644
--- a/src/calibre/ebooks/mobi/writer8/skeleton.py
+++ b/src/calibre/ebooks/mobi/writer8/skeleton.py
@@ -341,7 +341,7 @@ class Chunker:
for s in self.skeletons:
s.start_pos = sp
sp += len(s)
- self.skel_table = [Skel(s.file_number, 'SKEL%010d'%s.file_number,
+ self.skel_table = [Skel(s.file_number, f'SKEL{s.file_number:010}',
len(s.chunks), s.start_pos, len(s.skeleton)) for s in self.skeletons]
Chunk = namedtuple('Chunk',
@@ -426,13 +426,13 @@ class Chunker:
error = False
for i, skeleton in enumerate(self.skeletons):
for j, chunk in enumerate(skeleton.chunks):
- with open(os.path.join(chunks, 'file-%d-chunk-%d.html'%(i, j)),
+ with open(os.path.join(chunks, f'file-{i}-chunk-{j}.html'),
'wb') as f:
f.write(chunk.raw)
oraw, rraw = orig_dumps[i], skeleton.rebuild()
- with open(os.path.join(orig, '%04d.html'%i), 'wb') as f:
+ with open(os.path.join(orig, f'{i:04}.html'), 'wb') as f:
f.write(oraw)
- with open(os.path.join(rebuilt, '%04d.html'%i), 'wb') as f:
+ with open(os.path.join(rebuilt, f'{i:04}.html'), 'wb') as f:
f.write(rraw)
if oraw != rraw:
error = True
diff --git a/src/calibre/ebooks/odt/input.py b/src/calibre/ebooks/odt/input.py
index 6a616cc081..d2da2e104d 100644
--- a/src/calibre/ebooks/odt/input.py
+++ b/src/calibre/ebooks/odt/input.py
@@ -200,7 +200,7 @@ class Extract(ODF2XHTML):
# Replace all the class selectors with a single class selector
# This will be added to the class attribute of all elements
# that have one of these selectors.
- replace_name = 'c_odt%d'%count
+ replace_name = f'c_odt{count}'
count += 1
for sel in r.selectorList:
s = sel.selectorText[1:]
diff --git a/src/calibre/ebooks/oeb/iterator/bookmarks.py b/src/calibre/ebooks/oeb/iterator/bookmarks.py
index c410da57c3..6df8e487da 100644
--- a/src/calibre/ebooks/oeb/iterator/bookmarks.py
+++ b/src/calibre/ebooks/oeb/iterator/bookmarks.py
@@ -57,7 +57,7 @@ class BookmarksMixin:
dat = []
for bm in bookmarks:
if bm['type'] == 'legacy':
- rec = '%s^%d#%s'%(bm['title'], bm['spine'], bm['pos'])
+ rec = f"{bm['title']}^{bm['spine']}#{bm['pos']}"
else:
pos = bm['pos']
if isinstance(pos, numbers.Number):
diff --git a/src/calibre/ebooks/oeb/parse_utils.py b/src/calibre/ebooks/oeb/parse_utils.py
index 3649f27263..6ada357b5d 100644
--- a/src/calibre/ebooks/oeb/parse_utils.py
+++ b/src/calibre/ebooks/oeb/parse_utils.py
@@ -103,8 +103,7 @@ def html5_parse(data, max_nesting_depth=100):
if isinstance(x.tag, string_or_bytes) and not len(x): # Leaf node
depth = node_depth(x)
if depth > max_nesting_depth:
- raise ValueError('HTML 5 parsing resulted in a tree with nesting'
- ' depth > %d'%max_nesting_depth)
+ raise ValueError(f'HTML 5 parsing resulted in a tree with nesting depth > {max_nesting_depth}')
return data
diff --git a/src/calibre/ebooks/oeb/polish/check/links.py b/src/calibre/ebooks/oeb/polish/check/links.py
index d4beb97d38..e84ef6c27b 100644
--- a/src/calibre/ebooks/oeb/polish/check/links.py
+++ b/src/calibre/ebooks/oeb/polish/check/links.py
@@ -231,7 +231,7 @@ class MimetypeMismatch(BaseError):
c = 0
while container.has_name(new_name):
c += 1
- new_name = self.file_name.rpartition('.')[0] + ('%d.' % c) + self.change_ext_to
+ new_name = self.file_name.rpartition('.')[0] + f'{c}.' + self.change_ext_to
rename_files(container, {self.file_name:new_name})
changed = True
else:
diff --git a/src/calibre/ebooks/oeb/polish/check/parsing.py b/src/calibre/ebooks/oeb/polish/check/parsing.py
index 6fa5fd2623..5cfeeb04e9 100644
--- a/src/calibre/ebooks/oeb/polish/check/parsing.py
+++ b/src/calibre/ebooks/oeb/polish/check/parsing.py
@@ -146,7 +146,7 @@ class EscapedName(BaseError):
c = 0
while self.sname in all_names:
c += 1
- self.sname = '%s_%d.%s' % (bn, c, ext)
+ self.sname = f'{bn}_{c}.{ext}'
rename_files(container, {self.name:self.sname})
return True
diff --git a/src/calibre/ebooks/oeb/polish/container.py b/src/calibre/ebooks/oeb/polish/container.py
index caae69f660..49dca6605c 100644
--- a/src/calibre/ebooks/oeb/polish/container.py
+++ b/src/calibre/ebooks/oeb/polish/container.py
@@ -344,7 +344,7 @@ class Container(ContainerBase): # {{{
item_id = 'id'
while item_id in all_ids:
c += 1
- item_id = 'id' + '%d'%c
+ item_id = 'id' + f'{c}'
manifest = self.opf_xpath('//opf:manifest')[0]
href = self.name_to_href(name, self.opf_name)
item = manifest.makeelement(OPF('item'),
@@ -369,7 +369,7 @@ class Container(ContainerBase): # {{{
base, ext = name.rpartition('.')[::2]
if c > 1:
base = base.rpartition('-')[0]
- name = '%s-%d.%s' % (base, c, ext)
+ name = f'{base}-{c}.{ext}'
return name
def add_file(self, name, data, media_type=None, spine_index=None, modify_name_if_needed=False, process_manifest_item=None):
diff --git a/src/calibre/ebooks/oeb/polish/cover.py b/src/calibre/ebooks/oeb/polish/cover.py
index a356096bde..619a5316ad 100644
--- a/src/calibre/ebooks/oeb/polish/cover.py
+++ b/src/calibre/ebooks/oeb/polish/cover.py
@@ -382,7 +382,7 @@ def create_epub_cover(container, cover_path, existing_image, options=None):
container.log.exception('Failed to get width and height of cover')
ar = 'xMidYMid meet' if keep_aspect else 'none'
templ = CoverManager.SVG_TEMPLATE.replace('__ar__', ar)
- templ = templ.replace('__viewbox__', '0 0 %d %d'%(width, height))
+ templ = templ.replace('__viewbox__', f'0 0 {width} {height}')
templ = templ.replace('__width__', str(width))
templ = templ.replace('__height__', str(height))
folder = recommended_folders[tname]
diff --git a/src/calibre/ebooks/oeb/polish/images.py b/src/calibre/ebooks/oeb/polish/images.py
index 34ab4d4615..9a68fb52ec 100644
--- a/src/calibre/ebooks/oeb/polish/images.py
+++ b/src/calibre/ebooks/oeb/polish/images.py
@@ -98,7 +98,7 @@ def compress_images(container, report=None, names=None, jpeg_quality=None, webp_
if not keep_going:
abort.set()
progress_callback(0, num_to_process, '')
- [Worker(abort, 'CompressImage%d' % i, queue, results, jpeg_quality, webp_quality, pc) for i in range(min(detect_ncpus(), num_to_process))]
+ [Worker(abort, f'CompressImage{i}', queue, results, jpeg_quality, webp_quality, pc) for i in range(min(detect_ncpus(), num_to_process))]
queue.join()
before_total = after_total = 0
processed_num = 0
diff --git a/src/calibre/ebooks/oeb/polish/replace.py b/src/calibre/ebooks/oeb/polish/replace.py
index 29364ffae2..bcec3d86df 100644
--- a/src/calibre/ebooks/oeb/polish/replace.py
+++ b/src/calibre/ebooks/oeb/polish/replace.py
@@ -218,7 +218,7 @@ def replace_file(container, name, path, basename, force_mt=None):
b, e = nname.rpartition('.')[0::2]
while container.exists(nname):
count += 1
- nname = b + ('_%d.%s' % (count, e))
+ nname = b + f'_{count}.{e}'
rename_files(container, {name:nname})
mt = force_mt or container.guess_type(nname)
container.mime_map[nname] = mt
@@ -308,7 +308,7 @@ def rationalize_folders(container, folder_type_map):
while new_name in all_names or new_name in new_names:
c += 1
n, ext = bn.rpartition('.')[0::2]
- new_name = posixpath.join(folder, '%s_%d.%s' % (n, c, ext))
+ new_name = posixpath.join(folder, f'{n}_{c}.{ext}')
name_map[name] = new_name
new_names.add(new_name)
return name_map
diff --git a/src/calibre/ebooks/oeb/polish/split.py b/src/calibre/ebooks/oeb/polish/split.py
index f2ee464fb2..6f9783a376 100644
--- a/src/calibre/ebooks/oeb/polish/split.py
+++ b/src/calibre/ebooks/oeb/polish/split.py
@@ -215,7 +215,7 @@ def split(container, name, loc_or_xpath, before=True, totals=None):
nname, s = None, 0
while not nname or container.exists(nname):
s += 1
- nname = '%s_split%d.%s' % (base, s, ext)
+ nname = f'{base}_split{s}.{ext}'
manifest_item = container.generate_item(nname, media_type=container.mime_map[name])
bottom_name = container.href_to_name(manifest_item.get('href'), container.opf_name)
@@ -287,7 +287,7 @@ def multisplit(container, name, xpath, before=True):
current = name
all_names = [name]
for i in range(len(nodes)):
- current = split(container, current, '//*[@calibre-split-point="%d"]' % i, before=before)
+ current = split(container, current, f'//*[@calibre-split-point="{i}"]', before=before)
all_names.append(current)
for x in all_names:
@@ -345,7 +345,7 @@ def unique_anchor(seen_anchors, current):
ans = current
while ans in seen_anchors:
c += 1
- ans = '%s_%d' % (current, c)
+ ans = f'{current}_{c}'
return ans
diff --git a/src/calibre/ebooks/oeb/polish/tests/structure.py b/src/calibre/ebooks/oeb/polish/tests/structure.py
index 9c2f534885..2d23fb48c3 100644
--- a/src/calibre/ebooks/oeb/polish/tests/structure.py
+++ b/src/calibre/ebooks/oeb/polish/tests/structure.py
@@ -51,7 +51,7 @@ def create_epub(manifest, spine=(), guide=(), meta_cover=None, ver=3):
spine = [x[0] for x in manifest if guess_type(x[0]) in OEB_DOCS]
spine = ''.join(f'' for name in spine)
guide = ''.join(f'' for name, typ, title in guide)
- opf = OPF_TEMPLATE.format(manifest=mo, ver='%d.0'%ver, metadata=metadata, spine=spine, guide=guide)
+ opf = OPF_TEMPLATE.format(manifest=mo, ver=f'{ver}.0', metadata=metadata, spine=spine, guide=guide)
buf = BytesIO()
with ZipFile(buf, 'w', ZIP_STORED) as zf:
zf.writestr('META-INF/container.xml', b'''
@@ -79,7 +79,7 @@ class Structure(BaseTest):
ep = os.path.join(self.tdir, str(n) + 'book.epub')
with open(ep, 'wb') as f:
f.write(create_epub(*args, **kw).getvalue())
- c = get_container(ep, tdir=os.path.join(self.tdir, 'container%d' % n), tweak_mode=True)
+ c = get_container(ep, tdir=os.path.join(self.tdir, f'container{n}'), tweak_mode=True)
return c
def test_toc_detection(self):
diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py
index a7b7d34360..b315c65354 100644
--- a/src/calibre/ebooks/oeb/polish/toc.py
+++ b/src/calibre/ebooks/oeb/polish/toc.py
@@ -622,7 +622,7 @@ def create_ncx(toc, to_href, btitle, lang, uid):
def process_node(xml_parent, toc_parent):
for child in toc_parent:
play_order['c'] += 1
- point = etree.SubElement(xml_parent, NCX('navPoint'), id='num_%d' % play_order['c'],
+ point = etree.SubElement(xml_parent, NCX('navPoint'), id=f"num_{play_order['c']}",
playOrder=str(play_order['c']))
label = etree.SubElement(point, NCX('navLabel'))
title = child.title
@@ -853,7 +853,7 @@ def toc_to_html(toc, container, toc_name, title, lang=None):
li.append(a)
if len(toc) > 0:
parent = li.makeelement(XHTML('ul'))
- parent.set('class', 'level%d' % (style_level))
+ parent.set('class', f'level{style_level}')
li.append(parent)
a.tail = '\n\n' + (indent*(level+2))
parent.text = '\n'+(indent*(level+3))
@@ -909,7 +909,7 @@ def create_inline_toc(container, title=None):
name, c = 'toc.xhtml', 0
while container.has_name(name):
c += 1
- name = 'toc%d.xhtml' % c
+ name = f'toc{c}.xhtml'
container.add_file(name, raw, spine_index=0)
else:
with container.open(name, 'wb') as f:
diff --git a/src/calibre/ebooks/oeb/transforms/cover.py b/src/calibre/ebooks/oeb/transforms/cover.py
index d315172985..278a715ed6 100644
--- a/src/calibre/ebooks/oeb/transforms/cover.py
+++ b/src/calibre/ebooks/oeb/transforms/cover.py
@@ -142,7 +142,7 @@ class CoverManager:
# if self.preserve_aspect_ratio:
# width, height = 600, 800
self.svg_template = self.svg_template.replace('__viewbox__',
- '0 0 %d %d'%(width, height))
+ f'0 0 {width} {height}')
self.svg_template = self.svg_template.replace('__width__',
str(width))
self.svg_template = self.svg_template.replace('__height__',
diff --git a/src/calibre/ebooks/oeb/transforms/filenames.py b/src/calibre/ebooks/oeb/transforms/filenames.py
index 485862fe39..97e4bdae81 100644
--- a/src/calibre/ebooks/oeb/transforms/filenames.py
+++ b/src/calibre/ebooks/oeb/transforms/filenames.py
@@ -132,7 +132,7 @@ class UniqueFilenames: # {{{
c = 0
while True:
c += 1
- suffix = '_u%d'%c
+ suffix = f'_u{c}'
candidate = base + suffix + ext
if candidate not in self.seen_filenames:
return suffix
diff --git a/src/calibre/ebooks/oeb/transforms/page_margin.py b/src/calibre/ebooks/oeb/transforms/page_margin.py
index 587c84d1df..736bf82cb1 100644
--- a/src/calibre/ebooks/oeb/transforms/page_margin.py
+++ b/src/calibre/ebooks/oeb/transforms/page_margin.py
@@ -143,7 +143,7 @@ class RemoveFakeMargins:
for p in paras(body):
level = level_of(p, body)
- level = '%s_%d'%(barename(p.tag), level)
+ level = f'{barename(p.tag)}_{level}'
if level not in self.levels:
self.levels[level] = []
self.levels[level].append(p)
@@ -151,7 +151,7 @@ class RemoveFakeMargins:
remove = set()
for k, v in iteritems(self.levels):
num = len(v)
- self.log.debug('Found %d items of level:'%num, k)
+ self.log.debug(f'Found {num} items of level:', k)
level = int(k.split('_')[-1])
tag = k.split('_')[0]
if tag == 'p' and num < 25:
diff --git a/src/calibre/ebooks/oeb/transforms/rasterize.py b/src/calibre/ebooks/oeb/transforms/rasterize.py
index 9af0b58efe..5843b40723 100644
--- a/src/calibre/ebooks/oeb/transforms/rasterize.py
+++ b/src/calibre/ebooks/oeb/transforms/rasterize.py
@@ -217,8 +217,7 @@ class SVGRasterizer:
href = self.images[key]
else:
logger = self.oeb.logger
- logger.info('Rasterizing %r to %dx%d'
- % (svgitem.href, size.width(), size.height()))
+ logger.info(f'Rasterizing {svgitem.href!r} to {size.width()}x{size.height()}')
image = QImage(size, QImage.Format.Format_ARGB32_Premultiplied)
image.fill(QColor('white').rgb())
painter = QPainter(image)
diff --git a/src/calibre/ebooks/oeb/transforms/rescale.py b/src/calibre/ebooks/oeb/transforms/rescale.py
index f46ae1c823..0a2066e40d 100644
--- a/src/calibre/ebooks/oeb/transforms/rescale.py
+++ b/src/calibre/ebooks/oeb/transforms/rescale.py
@@ -80,8 +80,7 @@ class RescaleImages:
if scaled:
new_width = max(1, new_width)
new_height = max(1, new_height)
- self.log('Rescaling image from %dx%d to %dx%d'%(
- width, height, new_width, new_height), item.href)
+ self.log(f'Rescaling image from {width}x{height} to {new_width}x{new_height}', item.href)
try:
img = img.resize((new_width, new_height))
except Exception:
diff --git a/src/calibre/ebooks/oeb/transforms/split.py b/src/calibre/ebooks/oeb/transforms/split.py
index 933750e318..85381a000e 100644
--- a/src/calibre/ebooks/oeb/transforms/split.py
+++ b/src/calibre/ebooks/oeb/transforms/split.py
@@ -134,7 +134,7 @@ class Split:
page_breaks.sort(key=lambda x: int(x.get('pb_order')))
page_break_ids, page_breaks_ = [], []
for i, x in enumerate(page_breaks):
- x.set('id', x.get('id', 'calibre_pb_%d'%i))
+ x.set('id', x.get('id', f'calibre_pb_{i}'))
id = x.get('id')
try:
xp = XPath(f'//*[@id="{id}"]')
@@ -145,7 +145,7 @@ class Split:
# The id has both a quote and an apostrophe or some other
# Just replace it since I doubt its going to work anywhere else
# either
- id = 'calibre_pb_%d'%i
+ id = f'calibre_pb_{i}'
x.set('id', id)
xp = XPath(f'//*[@id={id!r}]')
page_breaks_.append((xp, x.get('pb_before', '0') == '1'))
@@ -221,7 +221,7 @@ class FlowSplitter:
for i, tree in enumerate(trees):
size = len(tostring(tree.getroot()))
if size > self.max_flow_size:
- self.log('\tFound large tree #%d'%i)
+ self.log(f'\tFound large tree #{i}')
lt_found = True
self.split_trees = []
self.split_to_size(tree)
@@ -366,11 +366,10 @@ class FlowSplitter:
elif size <= self.max_flow_size:
self.split_trees.append(t)
self.log.debug(
- '\t\t\tCommitted sub-tree #%d (%d KB)'%(
- len(self.split_trees), size/1024.))
+ f'\t\t\tCommitted sub-tree #{len(self.split_trees)} ({size / 1024.0} KB)')
else:
self.log.debug(
- '\t\t\tSplit tree still too large: %d KB' % (size/1024.))
+ f'\t\t\tSplit tree still too large: {size / 1024.0} KB')
self.split_to_size(t)
def find_split_point(self, root):
diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py
index ecaab38f41..36dcac9ede 100644
--- a/src/calibre/ebooks/oeb/transforms/structure.py
+++ b/src/calibre/ebooks/oeb/transforms/structure.py
@@ -69,8 +69,7 @@ class DetectStructure:
self.oeb.toc = orig_toc
else:
self.oeb.auto_generated_toc = True
- self.log('Auto generated TOC with %d entries.' %
- self.oeb.toc.count())
+ self.log(f'Auto generated TOC with {self.oeb.toc.count()} entries.')
if opts.toc_filter is not None:
regexp = re.compile(opts.toc_filter)
@@ -249,7 +248,7 @@ class DetectStructure:
text = elem.get('alt', '')
text = re.sub(r'\s+', ' ', text.strip())
text = text[:1000].strip()
- id = elem.get('id', 'calibre_toc_%d'%counter)
+ id = elem.get('id', f'calibre_toc_{counter}')
elem.set('id', id)
href = '#'.join((item.href, id))
return text, href
diff --git a/src/calibre/ebooks/pdb/ereader/inspector.py b/src/calibre/ebooks/pdb/ereader/inspector.py
index 938492f77d..34a15118c1 100644
--- a/src/calibre/ebooks/pdb/ereader/inspector.py
+++ b/src/calibre/ebooks/pdb/ereader/inspector.py
@@ -43,36 +43,36 @@ def pdb_header_info(header):
def ereader_header_info132(h0):
print('Ereader Record 0 (Header) Info:')
print('')
- print('0-2 Version: %i' % struct.unpack('>H', h0[0:2])[0])
- print('2-4: %i' % struct.unpack('>H', h0[2:4])[0])
- print('4-6: %i' % struct.unpack('>H', h0[4:6])[0])
- print('6-8 Codepage: %i' % struct.unpack('>H', h0[6:8])[0])
- print('8-10: %i' % struct.unpack('>H', h0[8:10])[0])
- print('10-12: %i' % struct.unpack('>H', h0[10:12])[0])
- print('12-14 Non-Text offset: %i' % struct.unpack('>H', h0[12:14])[0])
- print('14-16: %i' % struct.unpack('>H', h0[14:16])[0])
- print('16-18: %i' % struct.unpack('>H', h0[16:18])[0])
- print('18-20: %i' % struct.unpack('>H', h0[18:20])[0])
- print('20-22 Image Count: %i' % struct.unpack('>H', h0[20:22])[0])
- print('22-24: %i' % struct.unpack('>H', h0[22:24])[0])
- print('24-26 Has Metadata?: %i' % struct.unpack('>H', h0[24:26])[0])
- print('26-28: %i' % struct.unpack('>H', h0[26:28])[0])
- print('28-30 Footnote Count: %i' % struct.unpack('>H', h0[28:30])[0])
- print('30-32 Sidebar Count: %i' % struct.unpack('>H', h0[30:32])[0])
- print('32-34 Bookmark Offset: %i' % struct.unpack('>H', h0[32:34])[0])
- print('34-36 MAGIC: %i' % struct.unpack('>H', h0[34:36])[0])
- print('36-38: %i' % struct.unpack('>H', h0[36:38])[0])
- print('38-40: %i' % struct.unpack('>H', h0[38:40])[0])
- print('40-42 Image Data Offset: %i' % struct.unpack('>H', h0[40:42])[0])
- print('42-44: %i' % struct.unpack('>H', h0[42:44])[0])
- print('44-46 Metadata Offset: %i' % struct.unpack('>H', h0[44:46])[0])
- print('46-48: %i' % struct.unpack('>H', h0[46:48])[0])
- print('48-50 Footnote Offset: %i' % struct.unpack('>H', h0[48:50])[0])
- print('50-52 Sidebar Offset: %i' % struct.unpack('>H', h0[50:52])[0])
- print('52-54 Last Data Offset: %i' % struct.unpack('>H', h0[52:54])[0])
+ print(f"0-2 Version: {struct.unpack('>H', h0[0:2])[0]}")
+ print(f"2-4: {struct.unpack('>H', h0[2:4])[0]}")
+ print(f"4-6: {struct.unpack('>H', h0[4:6])[0]}")
+ print(f"6-8 Codepage: {struct.unpack('>H', h0[6:8])[0]}")
+ print(f"8-10: {struct.unpack('>H', h0[8:10])[0]}")
+ print(f"10-12: {struct.unpack('>H', h0[10:12])[0]}")
+ print(f"12-14 Non-Text offset: {struct.unpack('>H', h0[12:14])[0]}")
+ print(f"14-16: {struct.unpack('>H', h0[14:16])[0]}")
+ print(f"16-18: {struct.unpack('>H', h0[16:18])[0]}")
+ print(f"18-20: {struct.unpack('>H', h0[18:20])[0]}")
+ print(f"20-22 Image Count: {struct.unpack('>H', h0[20:22])[0]}")
+ print(f"22-24: {struct.unpack('>H', h0[22:24])[0]}")
+ print(f"24-26 Has Metadata?: {struct.unpack('>H', h0[24:26])[0]}")
+ print(f"26-28: {struct.unpack('>H', h0[26:28])[0]}")
+ print(f"28-30 Footnote Count: {struct.unpack('>H', h0[28:30])[0]}")
+ print(f"30-32 Sidebar Count: {struct.unpack('>H', h0[30:32])[0]}")
+ print(f"32-34 Bookmark Offset: {struct.unpack('>H', h0[32:34])[0]}")
+ print(f"34-36 MAGIC: {struct.unpack('>H', h0[34:36])[0]}")
+ print(f"36-38: {struct.unpack('>H', h0[36:38])[0]}")
+ print(f"38-40: {struct.unpack('>H', h0[38:40])[0]}")
+ print(f"40-42 Image Data Offset: {struct.unpack('>H', h0[40:42])[0]}")
+ print(f"42-44: {struct.unpack('>H', h0[42:44])[0]}")
+ print(f"44-46 Metadata Offset: {struct.unpack('>H', h0[44:46])[0]}")
+ print(f"46-48: {struct.unpack('>H', h0[46:48])[0]}")
+ print(f"48-50 Footnote Offset: {struct.unpack('>H', h0[48:50])[0]}")
+ print(f"50-52 Sidebar Offset: {struct.unpack('>H', h0[50:52])[0]}")
+ print(f"52-54 Last Data Offset: {struct.unpack('>H', h0[52:54])[0]}")
for i in range(54, 131, 2):
- print('%i-%i: %i' % (i, i+2, struct.unpack('>H', h0[i:i+2])[0]))
+ print(f"{i}-{i + 2}: {struct.unpack('>H', h0[i:i + 2])[0]}")
print('')
@@ -80,30 +80,30 @@ def ereader_header_info132(h0):
def ereader_header_info202(h0):
print('Ereader Record 0 (Header) Info:')
print('')
- print('0-2 Version: %i' % struct.unpack('>H', h0[0:2])[0])
- print('2-4 Garbage: %i' % struct.unpack('>H', h0[2:4])[0])
- print('4-6 Garbage: %i' % struct.unpack('>H', h0[4:6])[0])
- print('6-8 Garbage: %i' % struct.unpack('>H', h0[6:8])[0])
- print('8-10 Non-Text Offset: %i' % struct.unpack('>H', h0[8:10])[0])
- print('10-12: %i' % struct.unpack('>H', h0[10:12])[0])
- print('12-14: %i' % struct.unpack('>H', h0[12:14])[0])
- print('14-16 Garbage: %i' % struct.unpack('>H', h0[14:16])[0])
- print('16-18 Garbage: %i' % struct.unpack('>H', h0[16:18])[0])
- print('18-20 Garbage: %i' % struct.unpack('>H', h0[18:20])[0])
- print('20-22 Garbage: %i' % struct.unpack('>H', h0[20:22])[0])
- print('22-24 Garbage: %i' % struct.unpack('>H', h0[22:24])[0])
- print('24-26: %i' % struct.unpack('>H', h0[24:26])[0])
- print('26-28: %i' % struct.unpack('>H', h0[26:28])[0])
+ print(f"0-2 Version: {struct.unpack('>H', h0[0:2])[0]}")
+ print(f"2-4 Garbage: {struct.unpack('>H', h0[2:4])[0]}")
+ print(f"4-6 Garbage: {struct.unpack('>H', h0[4:6])[0]}")
+ print(f"6-8 Garbage: {struct.unpack('>H', h0[6:8])[0]}")
+ print(f"8-10 Non-Text Offset: {struct.unpack('>H', h0[8:10])[0]}")
+ print(f"10-12: {struct.unpack('>H', h0[10:12])[0]}")
+ print(f"12-14: {struct.unpack('>H', h0[12:14])[0]}")
+ print(f"14-16 Garbage: {struct.unpack('>H', h0[14:16])[0]}")
+ print(f"16-18 Garbage: {struct.unpack('>H', h0[16:18])[0]}")
+ print(f"18-20 Garbage: {struct.unpack('>H', h0[18:20])[0]}")
+ print(f"20-22 Garbage: {struct.unpack('>H', h0[20:22])[0]}")
+ print(f"22-24 Garbage: {struct.unpack('>H', h0[22:24])[0]}")
+ print(f"24-26: {struct.unpack('>H', h0[24:26])[0]}")
+ print(f"26-28: {struct.unpack('>H', h0[26:28])[0]}")
for i in range(28, 98, 2):
- print('%i-%i Garbage: %i' % (i, i+2, struct.unpack('>H', h0[i:i+2])[0]))
- print('98-100: %i' % struct.unpack('>H', h0[98:100])[0])
+ print(f"{i}-{i + 2} Garbage: {struct.unpack('>H', h0[i:i + 2])[0]}")
+ print(f"98-100: {struct.unpack('>H', h0[98:100])[0]}")
for i in range(100, 110, 2):
- print('%i-%i Garbage: %i' % (i, i+2, struct.unpack('>H', h0[i:i+2])[0]))
- print('110-112: %i' % struct.unpack('>H', h0[110:112])[0])
- print('112-114: %i' % struct.unpack('>H', h0[112:114])[0])
- print('114-116 Garbage: %i' % struct.unpack('>H', h0[114:116])[0])
+ print(f"{i}-{i + 2} Garbage: {struct.unpack('>H', h0[i:i + 2])[0]}")
+ print(f"110-112: {struct.unpack('>H', h0[110:112])[0]}")
+ print(f"112-114: {struct.unpack('>H', h0[112:114])[0]}")
+ print(f"114-116 Garbage: {struct.unpack('>H', h0[114:116])[0]}")
for i in range(116, 202, 2):
- print('%i-%i: %i' % (i, i+2, struct.unpack('>H', h0[i:i+2])[0]))
+ print(f"{i}-{i + 2}: {struct.unpack('>H', h0[i:i + 2])[0]}")
print('')
print('* Garbage: Random values.')
@@ -121,7 +121,7 @@ def section_lengths(header):
else:
message = ''
- print('Section %i: %i %s' % (i, size, message))
+ print(f'Section {i}: {size} {message}')
def main(args=sys.argv):
diff --git a/src/calibre/ebooks/pdb/ereader/reader132.py b/src/calibre/ebooks/pdb/ereader/reader132.py
index 48e8fd417a..a92fa3d25c 100644
--- a/src/calibre/ebooks/pdb/ereader/reader132.py
+++ b/src/calibre/ebooks/pdb/ereader/reader132.py
@@ -67,7 +67,7 @@ class Reader132(FormatReader):
if self.header_record.compression in (260, 272):
raise DRMError('eReader DRM is not supported.')
else:
- raise EreaderError('Unknown book compression %i.' % self.header_record.compression)
+ raise EreaderError(f'Unknown book compression {self.header_record.compression}.')
from calibre.ebooks.metadata.pdb import get_metadata
self.mi = get_metadata(stream, False)
@@ -116,7 +116,7 @@ class Reader132(FormatReader):
pml = ''
for i in range(1, self.header_record.num_text_pages + 1):
- self.log.debug('Extracting text page %i' % i)
+ self.log.debug(f'Extracting text page {i}')
pml += self.get_text_page(i)
hizer = PML_HTMLizer()
html += hizer.parse_pml(pml, 'index.html')
@@ -127,7 +127,7 @@ class Reader132(FormatReader):
footnoteids = re.findall(r'\w+(?=\x00)',
self.section_data(self.header_record.footnote_offset).decode('cp1252' if self.encoding is None else self.encoding))
for fid, i in enumerate(range(self.header_record.footnote_offset + 1, self.header_record.footnote_offset + self.header_record.footnote_count)):
- self.log.debug('Extracting footnote page %i' % i)
+ self.log.debug(f'Extracting footnote page {i}')
if fid < len(footnoteids):
fid = footnoteids[fid]
else:
@@ -139,7 +139,7 @@ class Reader132(FormatReader):
sidebarids = re.findall(r'\w+(?=\x00)',
self.section_data(self.header_record.sidebar_offset).decode('cp1252' if self.encoding is None else self.encoding))
for sid, i in enumerate(range(self.header_record.sidebar_offset + 1, self.header_record.sidebar_offset + self.header_record.sidebar_count)):
- self.log.debug('Extracting sidebar page %i' % i)
+ self.log.debug(f'Extracting sidebar page {i}')
if sid < len(sidebarids):
sid = sidebarids[sid]
else:
diff --git a/src/calibre/ebooks/pdb/ereader/reader202.py b/src/calibre/ebooks/pdb/ereader/reader202.py
index ebf69f8d92..259bfe00ec 100644
--- a/src/calibre/ebooks/pdb/ereader/reader202.py
+++ b/src/calibre/ebooks/pdb/ereader/reader202.py
@@ -45,7 +45,7 @@ class Reader202(FormatReader):
self.header_record = HeaderRecord(self.section_data(0))
if self.header_record.version not in (2, 4):
- raise EreaderError('Unknown book version %i.' % self.header_record.version)
+ raise EreaderError(f'Unknown book version {self.header_record.version}.')
from calibre.ebooks.metadata.pdb import get_metadata
self.mi = get_metadata(stream, False)
@@ -91,7 +91,7 @@ class Reader202(FormatReader):
pml = ''
for i in range(1, self.header_record.num_text_pages + 1):
- self.log.debug('Extracting text page %i' % i)
+ self.log.debug(f'Extracting text page {i}')
pml += self.get_text_page(i)
title = self.mi.title
diff --git a/src/calibre/ebooks/pdb/haodoo/reader.py b/src/calibre/ebooks/pdb/haodoo/reader.py
index 420f4dc1eb..6f36e6d27d 100644
--- a/src/calibre/ebooks/pdb/haodoo/reader.py
+++ b/src/calibre/ebooks/pdb/haodoo/reader.py
@@ -119,7 +119,7 @@ class Reader(FormatReader):
self.log.info('Decompressing text...')
for i in range(1, self.header_record.num_records + 1):
- self.log.debug('\tDecompressing text section %i' % i)
+ self.log.debug(f'\tDecompressing text section {i}')
title = self.header_record.chapter_titles[i-1]
lines = []
title_added = False
diff --git a/src/calibre/ebooks/pdb/header.py b/src/calibre/ebooks/pdb/header.py
index 2e72faf3b8..7b3f834702 100644
--- a/src/calibre/ebooks/pdb/header.py
+++ b/src/calibre/ebooks/pdb/header.py
@@ -36,7 +36,7 @@ class PdbHeaderReader:
def full_section_info(self, number):
if not (0 <= number < self.num_sections):
- raise ValueError('Not a valid section number %i' % number)
+ raise ValueError(f'Not a valid section number {number}')
self.stream.seek(78 + number * 8)
offset, a1, a2, a3, a4 = struct.unpack('>LBBBB', self.stream.read(8))[0]
@@ -45,14 +45,14 @@ class PdbHeaderReader:
def section_offset(self, number):
if not (0 <= number < self.num_sections):
- raise ValueError('Not a valid section number %i' % number)
+ raise ValueError(f'Not a valid section number {number}')
self.stream.seek(78 + number * 8)
return struct.unpack('>LBBBB', self.stream.read(8))[0]
def section_data(self, number):
if not (0 <= number < self.num_sections):
- raise ValueError('Not a valid section number %i' % number)
+ raise ValueError(f'Not a valid section number {number}')
start = self.section_offset(number)
if number == self.num_sections -1:
diff --git a/src/calibre/ebooks/pdb/palmdoc/reader.py b/src/calibre/ebooks/pdb/palmdoc/reader.py
index 463e5d0115..797356138d 100644
--- a/src/calibre/ebooks/pdb/palmdoc/reader.py
+++ b/src/calibre/ebooks/pdb/palmdoc/reader.py
@@ -54,7 +54,7 @@ class Reader(FormatReader):
self.log.info('Decompressing text...')
for i in range(1, self.header_record.num_records + 1):
- self.log.debug('\tDecompressing text section %i' % i)
+ self.log.debug(f'\tDecompressing text section {i}')
raw_txt += self.decompress_text(i)
self.log.info('Converting text to OEB...')
diff --git a/src/calibre/ebooks/pdb/palmdoc/writer.py b/src/calibre/ebooks/pdb/palmdoc/writer.py
index 8d6e4e1504..cf9e03cae6 100644
--- a/src/calibre/ebooks/pdb/palmdoc/writer.py
+++ b/src/calibre/ebooks/pdb/palmdoc/writer.py
@@ -33,7 +33,7 @@ class Writer(FormatWriter):
section_lengths = [len(header_record)]
self.log.info('Compessing data...')
for i in range(len(txt_records)):
- self.log.debug('\tCompressing record %i' % i)
+ self.log.debug(f'\tCompressing record {i}')
txt_records[i] = compress_doc(txt_records[i])
section_lengths.append(len(txt_records[i]))
diff --git a/src/calibre/ebooks/pdb/ztxt/reader.py b/src/calibre/ebooks/pdb/ztxt/reader.py
index 56678193e7..54141c6901 100644
--- a/src/calibre/ebooks/pdb/ztxt/reader.py
+++ b/src/calibre/ebooks/pdb/ztxt/reader.py
@@ -48,13 +48,12 @@ class Reader(FormatReader):
vmajor = (self.header_record.version & 0x0000FF00) >> 8
vminor = self.header_record.version & 0x000000FF
if vmajor < 1 or (vmajor == 1 and vminor < 40):
- raise zTXTError('Unsupported ztxt version (%i.%i). Only versions newer than %i.%i are supported.' %
- (vmajor, vminor, SUPPORTED_VERSION[0], SUPPORTED_VERSION[1]))
+ raise zTXTError(f'Unsupported ztxt version ({vmajor}.{vminor}). Only versions newer than {SUPPORTED_VERSION[0]}.{SUPPORTED_VERSION[1]} are supported.')
if (self.header_record.flags & 0x01) == 0:
raise zTXTError('Only compression method 1 (random access) is supported')
- self.log.debug('Foud ztxt version: %i.%i' % (vmajor, vminor))
+ self.log.debug(f'Foud ztxt version: {vmajor}.{vminor}')
# Initialize the decompressor
self.uncompressor = zlib.decompressobj()
@@ -73,7 +72,7 @@ class Reader(FormatReader):
self.log.info('Decompressing text...')
for i in range(1, self.header_record.num_records + 1):
- self.log.debug('\tDecompressing text section %i' % i)
+ self.log.debug(f'\tDecompressing text section {i}')
raw_txt += self.decompress_text(i)
self.log.info('Converting text to OEB...')
diff --git a/src/calibre/ebooks/pdb/ztxt/writer.py b/src/calibre/ebooks/pdb/ztxt/writer.py
index c9e8f81d51..df0ce2a034 100644
--- a/src/calibre/ebooks/pdb/ztxt/writer.py
+++ b/src/calibre/ebooks/pdb/ztxt/writer.py
@@ -33,7 +33,7 @@ class Writer(FormatWriter):
compressor = zlib.compressobj(9)
self.log.info('Compressing data...')
for i in range(len(txt_records)):
- self.log.debug('\tCompressing record %i' % i)
+ self.log.debug(f'\tCompressing record {i}')
txt_records[i] = compressor.compress(txt_records[i])
txt_records[i] = txt_records[i] + compressor.flush(zlib.Z_FULL_FLUSH)
section_lengths.append(len(txt_records[i]))
diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py
index 813feeb0f6..48abf79d19 100644
--- a/src/calibre/ebooks/pdf/html_writer.py
+++ b/src/calibre/ebooks/pdf/html_writer.py
@@ -795,7 +795,7 @@ def add_pagenum_toc(root, toc, opts, page_number_display_map):
E('h2', text=(opts.toc_title or _('Table of Contents')), parent=body)
table = E('table', parent=body)
for level, node in toc.iterdescendants(level=0):
- tr = E('tr', cls='level-%d' % level, parent=table)
+ tr = E('tr', cls=f'level-{level}', parent=table)
E('td', text=node.title or _('Unknown'), parent=tr)
num = node.pdf_loc.pagenum
num = page_number_display_map.get(num, num)
diff --git a/src/calibre/ebooks/pdf/pdftohtml.py b/src/calibre/ebooks/pdf/pdftohtml.py
index 9324d3fb24..ac84dbafc5 100644
--- a/src/calibre/ebooks/pdf/pdftohtml.py
+++ b/src/calibre/ebooks/pdf/pdftohtml.py
@@ -73,7 +73,7 @@ def pdftohtml(output_dir, pdf_path, no_images, as_xml=False):
logf.close()
out = open(logf.name, 'rb').read().decode('utf-8', 'replace').strip()
if ret != 0:
- raise ConversionError('pdftohtml failed with return code: %d\n%s' % (ret, out))
+ raise ConversionError(f'pdftohtml failed with return code: {ret}\n{out}')
if out:
prints('pdftohtml log:')
prints(out)
diff --git a/src/calibre/ebooks/pdf/reflow.py b/src/calibre/ebooks/pdf/reflow.py
index de2b1a2d40..abcf8a7883 100644
--- a/src/calibre/ebooks/pdf/reflow.py
+++ b/src/calibre/ebooks/pdf/reflow.py
@@ -226,8 +226,7 @@ class Text(Element):
def coalesce(self, other, page_number, left_margin, right_margin):
if self.opts.verbose > 2:
- self.log.debug('Coalescing %r with %r on page %d'%(self.text_as_string,
- other.text_as_string, page_number))
+ self.log.debug(f'Coalescing {self.text_as_string!r} with {other.text_as_string!r} on page {page_number}')
# Need to work out how to decide this
# For elements of the same line, is there a space between?
has_float = ''
@@ -534,7 +533,7 @@ class Column:
return self.elements[idx-1]
def dump(self, f, num):
- f.write('******** Column %d\n\n'%num)
+ f.write(f'******** Column {num}\n\n')
for elem in self.elements:
elem.dump(f)
@@ -548,7 +547,7 @@ class Box(list):
ans = [f'<{self.tag}>']
for elem in self:
if isinstance(elem, int):
- ans.append(''%elem)
+ ans.append(f'')
else:
ans.append(elem.to_html()+' ')
ans.append(f'{self.tag}>')
@@ -568,7 +567,7 @@ class ImageBox(Box):
ans.append('
')
for elem in self:
if isinstance(elem, int):
- ans.append(''%elem)
+ ans.append(f'')
else:
ans.append(elem.to_html()+' ')
ans.append('')
@@ -759,7 +758,7 @@ class Page:
self.number = int(page.get('number'))
self.odd_even = self.number % 2 # Odd = 1
self.top, self.left, self.width, self.height = map(float, map(page.get, ('top', 'left', 'width', 'height')))
- self.id = 'page%d'%self.number
+ self.id = f'page{self.number}'
self.page_break_after = False
self.texts = []
@@ -1210,7 +1209,7 @@ class Page:
self.regions.append(current_region)
if self.opts.verbose > 2:
- self.debug_dir = 'page-%d'%self.number
+ self.debug_dir = f'page-{self.number}'
os.mkdir(self.debug_dir)
self.dump_regions('pre-coalesce')
@@ -1221,7 +1220,7 @@ class Page:
def dump_regions(self, fname):
fname = 'regions-'+fname+'.txt'
with open(os.path.join(self.debug_dir, fname), 'wb') as f:
- f.write('Page #%d\n\n'%self.number)
+ f.write(f'Page #{self.number}\n\n')
for region in self.regions:
region.dump(f)
@@ -1369,7 +1368,7 @@ class Page:
ans[-1] += ' style="text-align:center"'
if self.id_used == 0:
self.id_used = 1
- ans[-1] += ' id="page_%d"'%self.number
+ ans[-1] += f' id="page_{self.number}"'
ans[-1] += '>'
ans[-1] += self.imgs[iind].to_html()
ans[-1] += '
'
@@ -1382,7 +1381,7 @@ class Page:
# and text.tag[0] == 'h'
if self.id_used == 0:
self.id_used = 1
- ans[-1] += ' id="page_%d"'%self.number
+ ans[-1] += f' id="page_{self.number}"'
if text.align == 'C':
ans[-1] += ' style="text-align:center"'
elif text.align == 'R':
@@ -1414,7 +1413,7 @@ class Page:
ans[-1] += ' style="text-align:center"'
if self.id_used == 0:
self.id_used = 1
- ans[-1] += ' id="page_%d"'%self.number
+ ans[-1] += f' id="page_{self.number}"'
ans[-1] += '>'
ans[-1] += self.imgs[iind].to_html()
ans[-1] += ''
diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py
index faaa66b50e..615a3a89b9 100644
--- a/src/calibre/ebooks/pdf/render/common.py
+++ b/src/calibre/ebooks/pdf/render/common.py
@@ -226,11 +226,11 @@ class Reference:
self.num, self.obj = num, obj
def pdf_serialize(self, stream):
- raw = '%d 0 R'%self.num
+ raw = f'{self.num} 0 R'
stream.write(raw.encode('ascii'))
def __repr__(self):
- return '%d 0 R'%self.num
+ return f'{self.num} 0 R'
def __str__(self):
return repr(self)
diff --git a/src/calibre/ebooks/pdf/render/graphics.py b/src/calibre/ebooks/pdf/render/graphics.py
index eeeda7394d..aaa1381b0c 100644
--- a/src/calibre/ebooks/pdf/render/graphics.py
+++ b/src/calibre/ebooks/pdf/render/graphics.py
@@ -419,17 +419,17 @@ class Graphics:
# Line cap
cap = {Qt.PenCapStyle.FlatCap:0, Qt.PenCapStyle.RoundCap:1, Qt.PenCapStyle.SquareCap:
2}.get(pen.capStyle(), 0)
- pdf.current_page.write('%d J '%cap)
+ pdf.current_page.write(f'{cap} J ')
# Line join
join = {Qt.PenJoinStyle.MiterJoin:0, Qt.PenJoinStyle.RoundJoin:1,
Qt.PenJoinStyle.BevelJoin:2}.get(pen.joinStyle(), 0)
- pdf.current_page.write('%d j '%join)
+ pdf.current_page.write(f'{join} j ')
# Dash pattern
if pen.style() == Qt.PenStyle.CustomDashLine:
pdf.serialize(Array(pen.dashPattern()))
- pdf.current_page.write(' %d d ' % pen.dashOffset())
+ pdf.current_page.write(f' {pen.dashOffset()} d ')
else:
ps = {Qt.PenStyle.DashLine:[3], Qt.PenStyle.DotLine:[1,2], Qt.PenStyle.DashDotLine:[3,2,1,2],
Qt.PenStyle.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), [])
diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py
index a6c1538ca5..581b1f81e1 100644
--- a/src/calibre/ebooks/pdf/render/serialize.py
+++ b/src/calibre/ebooks/pdf/render/serialize.py
@@ -43,7 +43,7 @@ class IndirectObjects:
def write_obj(self, stream, num, obj):
stream.write(EOL)
self._offsets[num-1] = stream.tell()
- stream.write('%d 0 obj'%num)
+ stream.write(f'{num} 0 obj')
stream.write(EOL)
serialize(obj, stream)
if stream.last_char != EOL:
@@ -66,13 +66,13 @@ class IndirectObjects:
def write_xref(self, stream):
self.xref_offset = stream.tell()
stream.write(b'xref'+EOL)
- stream.write('0 %d'%(1+len(self._offsets)))
+ stream.write(f'0 {1 + len(self._offsets)}')
stream.write(EOL)
stream.write('%010d 65535 f '%0)
stream.write(EOL)
for offset in self._offsets:
- line = '%010d 00000 n '%offset
+ line = f'{offset:010} 00000 n '
stream.write(line.encode('ascii') + EOL)
return self.xref_offset
@@ -526,5 +526,5 @@ class PDFStream:
'ID':Array([file_id, file_id]), 'Info':inforef})
serialize(trailer, self.stream)
self.write_line('startxref')
- self.write_line('%d'%startxref)
+ self.write_line(f'{startxref}')
self.stream.write('%%EOF')
diff --git a/src/calibre/ebooks/pml/__init__.py b/src/calibre/ebooks/pml/__init__.py
index 569247ccba..22843833bf 100644
--- a/src/calibre/ebooks/pml/__init__.py
+++ b/src/calibre/ebooks/pml/__init__.py
@@ -54,7 +54,7 @@ def unipmlcode(char):
try:
val = ord(char.encode('cp1252'))
if val in A_CHARS:
- return '\\a%i' % val
+ return f'\\a{val}'
except Exception:
pass
val = ord(char)
diff --git a/src/calibre/ebooks/readability/debug.py b/src/calibre/ebooks/readability/debug.py
index eeeee2cf48..e34b60c4d9 100644
--- a/src/calibre/ebooks/readability/debug.py
+++ b/src/calibre/ebooks/readability/debug.py
@@ -22,7 +22,7 @@ def describe(node, depth=2):
uid = uids[node] = len(uids)+1
else:
uid = uids.get(node)
- name += '%02d' % (uid)
+ name += f'{uid:02}'
if depth and node.getparent() is not None:
return name+' - '+describe(node.getparent(), depth-1)
return name
diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py
index a8a0802398..666c1481bb 100644
--- a/src/calibre/ebooks/rtf/input.py
+++ b/src/calibre/ebooks/rtf/input.py
@@ -27,11 +27,11 @@ class InlineClass(etree.XSLTExtension):
if fs:
if fs not in self.font_sizes:
self.font_sizes.append(fs)
- classes.append('fs%d'%self.font_sizes.index(fs))
+ classes.append(f'fs{self.font_sizes.index(fs)}')
fc = input_node.get('font-color', False)
if fc:
if fc not in self.colors:
self.colors.append(fc)
- classes.append('col%d'%self.colors.index(fc))
+ classes.append(f'col{self.colors.index(fc)}')
output_parent.text = ' '.join(classes)
diff --git a/src/calibre/ebooks/rtf/preprocess.py b/src/calibre/ebooks/rtf/preprocess.py
index 66e90106c6..460c4752b1 100644
--- a/src/calibre/ebooks/rtf/preprocess.py
+++ b/src/calibre/ebooks/rtf/preprocess.py
@@ -308,7 +308,7 @@ class RtfTokenizer:
i = i + 1
if not consumed:
- raise Exception('Error (at:%d): Control Word without end.'%(tokenStart))
+ raise Exception(f'Error (at:{tokenStart}): Control Word without end.')
# we have numeric argument before delimiter
if isChar(self.rtfData[i], '-') or isDigit(self.rtfData[i]):
diff --git a/src/calibre/ebooks/rtf2xml/check_encoding.py b/src/calibre/ebooks/rtf2xml/check_encoding.py
index 80425551e0..42f0db0305 100644
--- a/src/calibre/ebooks/rtf2xml/check_encoding.py
+++ b/src/calibre/ebooks/rtf2xml/check_encoding.py
@@ -30,7 +30,7 @@ class CheckEncoding:
if len(line) < 1000:
self.__get_position_error(line, encoding, line_num)
else:
- sys.stderr.write('line: %d has bad encoding\n' % line_num)
+ sys.stderr.write(f'line: {line_num} has bad encoding\n')
return True
return False
diff --git a/src/calibre/ebooks/rtf2xml/colors.py b/src/calibre/ebooks/rtf2xml/colors.py
index bbc2323baf..ef42033eee 100644
--- a/src/calibre/ebooks/rtf2xml/colors.py
+++ b/src/calibre/ebooks/rtf2xml/colors.py
@@ -211,7 +211,7 @@ class Colors:
if hex_num is None:
hex_num = '0'
if self.__run_level > 3:
- msg = 'no value in self.__color_dict for key %s at line %d\n' % (num, self.__line)
+ msg = f'no value in self.__color_dict for key {num} at line {self.__line}\n'
raise self.__bug_handler(msg)
return hex_num
diff --git a/src/calibre/ebooks/rtf2xml/footnote.py b/src/calibre/ebooks/rtf2xml/footnote.py
index 138cac80e9..ad67536d5b 100644
--- a/src/calibre/ebooks/rtf2xml/footnote.py
+++ b/src/calibre/ebooks/rtf2xml/footnote.py
@@ -83,9 +83,9 @@ class Footnote:
self.__cb_count = 0
self.__footnote_bracket_count = self.__ob_count
self.__write_obj.write(
- 'mi{num_string}'
self.__write_obj.write(f'mi%03d\n" % self.__pict_count)
write_obj.write('mi%03d\n' % self.__pict_count)
+ write_obj.write(f'mi{self.__pict_count:03}\n')
write_obj.write('mi -1:
- msg = '\nInvalid RTF: token "\\ " not valid.\nError at line %d'\
- % line_count
+ msg = f'\nInvalid RTF: token "\\ " not valid.\nError at line {line_count}'
raise self.__exception_handler(msg)
elif token[:1] == '\\':
line = self.process_cw(token)
diff --git a/src/calibre/ebooks/snb/snbfile.py b/src/calibre/ebooks/snb/snbfile.py
index f5e8974d99..8388d61492 100644
--- a/src/calibre/ebooks/snb/snbfile.py
+++ b/src/calibre/ebooks/snb/snbfile.py
@@ -283,7 +283,7 @@ class SNBFile:
if self.fileName:
print('File Name:\t', self.fileName)
print('File Count:\t', self.fileCount)
- print('VFAT Size(Compressed):\t%d(%d)' % (self.vfatSize, self.vfatCompressed))
+ print(f'VFAT Size(Compressed):\t{self.vfatSize}({self.vfatCompressed})')
print('Binary Stream Size:\t', self.binStreamSize)
print('Plain Stream Uncompressed Size:\t', self.plainStreamSizeUncompressed)
print('Binary Block Count:\t', self.binBlock)
diff --git a/src/calibre/ebooks/textile/functions.py b/src/calibre/ebooks/textile/functions.py
index c9e685dde9..f4a1f57b9c 100644
--- a/src/calibre/ebooks/textile/functions.py
+++ b/src/calibre/ebooks/textile/functions.py
@@ -565,9 +565,7 @@ class Textile:
h_match = re.search(r'h([1-6])', tag)
if h_match:
head_level, = h_match.groups()
- tag = 'h%i' % max(1,
- min(int(head_level) + head_offset,
- 6))
+ tag = f'h{max(1, min(int(head_level) + head_offset, 6))}'
o1, o2, content, c2, c1 = self.fBlock(tag, atts, ext,
cite, graf)
# leave off c1 if this block is extended,
diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py
index b4d735a5b7..e274f835d4 100644
--- a/src/calibre/gui2/actions/choose_library.py
+++ b/src/calibre/gui2/actions/choose_library.py
@@ -325,7 +325,7 @@ class ChooseLibraryAction(InterfaceAction):
self.switch_actions = []
for i in range(5):
ac = self.create_action(spec=('', None, None, None),
- attr='switch_action%d'%i)
+ attr=f'switch_action{i}')
ac.setObjectName(str(i))
self.switch_actions.append(ac)
ac.setVisible(False)
diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py
index b933bd6607..e268243f8b 100644
--- a/src/calibre/gui2/actions/delete.py
+++ b/src/calibre/gui2/actions/delete.py
@@ -46,7 +46,7 @@ class MultiDeleter(QObject): # {{{
self.cleanup()
return
id_ = self.ids.pop()
- title = 'id:%d'%id_
+ title = f'id:{id_}'
try:
title_ = self.model.db.title(id_, index_is_id=True)
if title_:
diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py
index 762621678b..eee459d3ec 100644
--- a/src/calibre/gui2/actions/edit_metadata.py
+++ b/src/calibre/gui2/actions/edit_metadata.py
@@ -333,10 +333,10 @@ class EditMetadataAction(InterfaceActionWithLibraryDrop):
id_map = {}
for bid in good_ids:
- opf = os.path.join(tdir, '%d.mi'%bid)
+ opf = os.path.join(tdir, f'{bid}.mi')
if not os.path.exists(opf):
opf = None
- cov = os.path.join(tdir, '%d.cover'%bid)
+ cov = os.path.join(tdir, f'{bid}.cover')
if not os.path.exists(cov):
cov = None
id_map[bid] = (opf, cov)
diff --git a/src/calibre/gui2/actions/mark_books.py b/src/calibre/gui2/actions/mark_books.py
index acdb7cd800..d2b94308f5 100644
--- a/src/calibre/gui2/actions/mark_books.py
+++ b/src/calibre/gui2/actions/mark_books.py
@@ -147,7 +147,7 @@ class MarkBooksAction(InterfaceActionWithLibraryDrop):
db = self.gui.current_db
marked_ids = db.data.marked_ids
num = len(frozenset(marked_ids).intersection(db.new_api.all_book_ids()))
- text = _('Show marked book') if num == 1 else (_('Show marked books') + (' (%d)' % num))
+ text = _('Show marked book') if num == 1 else (_('Show marked books') + f' ({num})')
self.show_marked_action.setText(text)
counts = {}
for v in marked_ids.values():
diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py
index 2addfc4b5f..82f228c4a0 100644
--- a/src/calibre/gui2/actions/tweak_epub.py
+++ b/src/calibre/gui2/actions/tweak_epub.py
@@ -140,7 +140,7 @@ class TweakEpubAction(InterfaceActionWithLibraryDrop):
self.gui.setCursor(Qt.CursorShape.BusyCursor)
if tprefs['update_metadata_from_calibre']:
db.new_api.embed_metadata((book_id,), only_fmts={fmt})
- notify = '%d:%s:%s:%s' % (book_id, fmt, db.library_id, db.library_path)
+ notify = f'{book_id}:{fmt}:{db.library_id}:{db.library_path}'
self.gui.job_manager.launch_gui_app('ebook-edit', kwargs=dict(path=path, notify=notify))
time.sleep(2)
finally:
diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 84f1f7eafc..538e0c9dd3 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -861,7 +861,7 @@ class CoverView(QWidget): # {{{
f = p.font()
f.setBold(True)
p.setFont(f)
- sz = '\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
+ sz = f'\xa0{self.pixmap.width()} x {self.pixmap.height()}\xa0'
flags = Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight|Qt.TextFlag.TextSingleLine
szrect = p.boundingRect(sztgt, flags, sz)
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py
index 2c792d53c3..22887d8aee 100644
--- a/src/calibre/gui2/catalog/catalog_epub_mobi.py
+++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py
@@ -976,7 +976,7 @@ class GenericRulesTable(QTableWidget):
self.setFocus()
row = self.last_row_selected + 1
if self.DEBUG:
- print('%s:add_row(): at row: %d' % (self.objectName(), row))
+ print(f'{self.objectName()}:add_row(): at row: {row}')
self.insertRow(row)
self.populate_table_row(row, self.create_blank_row_data())
self.select_and_scroll_to_row(row)
@@ -1029,8 +1029,7 @@ class GenericRulesTable(QTableWidget):
self.select_and_scroll_to_row(row)
self.settings_changed('enabled_state_changed')
if self.DEBUG:
- print('%s:enabled_state_changed(): row %d col %d' %
- (self.objectName(), row, col))
+ print(f'{self.objectName()}:enabled_state_changed(): row {row} col {col}')
def focusInEvent(self, e):
if self.DEBUG:
@@ -1042,7 +1041,7 @@ class GenericRulesTable(QTableWidget):
self.last_rows_selected = self.selectionModel().selectedRows()
self.clearSelection()
if self.DEBUG:
- print('%s:focusOutEvent(): self.last_row_selected: %d' % (self.objectName(),self.last_row_selected))
+ print(f'{self.objectName()}:focusOutEvent(): self.last_row_selected: {self.last_row_selected}')
def move_row_down(self):
self.setFocus()
@@ -1058,7 +1057,7 @@ class GenericRulesTable(QTableWidget):
dest_row = selrow.row() + 1
src_row = selrow.row()
if self.DEBUG:
- print('%s:move_row_down() %d -> %d' % (self.objectName(),src_row, dest_row))
+ print(f'{self.objectName()}:move_row_down() {src_row} -> {dest_row}')
# Save the contents of the destination row
saved_data = self.convert_row_to_data(dest_row)
@@ -1088,7 +1087,7 @@ class GenericRulesTable(QTableWidget):
for selrow in rows:
if self.DEBUG:
- print('%s:move_row_up() %d -> %d' % (self.objectName(),selrow.row(), selrow.row()-1))
+ print(f'{self.objectName()}:move_row_up() {selrow.row()} -> {selrow.row() - 1}')
# Save the row above
saved_data = self.convert_row_to_data(selrow.row() - 1)
@@ -1150,8 +1149,7 @@ class GenericRulesTable(QTableWidget):
break
if self.DEBUG:
- print('%s:_source_index_changed(): calling source_index_changed with row: %d ' %
- (self.objectName(), row))
+ print(f'{self.objectName()}:_source_index_changed(): calling source_index_changed with row: {row} ')
self.source_index_changed(combo, row)
@@ -1191,8 +1189,7 @@ class GenericRulesTable(QTableWidget):
break
if self.DEBUG:
- print('%s:values_index_changed(): row %d ' %
- (self.objectName(), row))
+ print(f'{self.objectName()}:values_index_changed(): row {row} ')
class ExclusionRules(GenericRulesTable):
diff --git a/src/calibre/gui2/convert/search_and_replace.py b/src/calibre/gui2/convert/search_and_replace.py
index df328723db..52b9c6ec52 100644
--- a/src/calibre/gui2/convert/search_and_replace.py
+++ b/src/calibre/gui2/convert/search_and_replace.py
@@ -28,7 +28,7 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
# Dummy attributes to fool the Widget() option handler code. We handle
# everything in our *handler methods.
for i in range(1, 4):
- x = 'sr%d_'%i
+ x = f'sr{i}_'
for y in ('search', 'replace'):
z = x + y
setattr(self, 'opt_'+z, z)
@@ -282,7 +282,7 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
self.set_value(self.opt_search_replace, None)
if new_val is None and legacy:
for i in range(1, 4):
- x = 'sr%d'%i
+ x = f'sr{i}'
s, r = x+'_search', x+'_replace'
s, r = legacy.get(s, ''), legacy.get(r, '')
if s:
diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py
index 494ed7590b..de1866ee89 100644
--- a/src/calibre/gui2/cover_flow.py
+++ b/src/calibre/gui2/cover_flow.py
@@ -57,7 +57,7 @@ class FileSystemImages(pictureflow.FlowImages):
if not img.isNull():
self.images.append(img)
self.captions.append(os.path.basename(f))
- self.subtitles.append('%d bytes'%os.stat(f).st_size)
+ self.subtitles.append(f'{os.stat(f).st_size} bytes')
def count(self):
return len(self.images)
@@ -91,7 +91,7 @@ class DummyImageList(pictureflow.FlowImages):
return self.images[index%2]
def caption(self, index):
- return 'Number: %d'%index
+ return f'Number: {index}'
def subtitle(self, index):
return ''
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index a4e8c9d7d4..2a4627df59 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -1428,8 +1428,7 @@ class DeviceMixin: # {{{
if not isinstance(prefix, str):
prefix = prefix.decode(preferred_encoding, 'replace')
prefix = ascii_filename(prefix)
- names.append('%s_%d%s'%(prefix, book_id,
- os.path.splitext(files[-1])[1]))
+ names.append(f'{prefix}_{book_id}{os.path.splitext(files[-1])[1]}')
self.update_thumbnail(mi)
dynamic.set('catalogs_to_be_synced', set())
if files:
@@ -1506,8 +1505,7 @@ class DeviceMixin: # {{{
if not isinstance(prefix, str):
prefix = prefix.decode(preferred_encoding, 'replace')
prefix = ascii_filename(prefix)
- names.append('%s_%d%s'%(prefix, book_id,
- os.path.splitext(files[-1])[1]))
+ names.append(f'{prefix}_{book_id}{os.path.splitext(files[-1])[1]}')
self.update_thumbnail(mi)
self.news_to_be_synced = set()
if config['upload_news_to_device'] and files:
@@ -1585,7 +1583,7 @@ class DeviceMixin: # {{{
if not isinstance(prefix, str):
prefix = prefix.decode(preferred_encoding, 'replace')
prefix = ascii_filename(prefix)
- names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
+ names.append(f'{prefix}_{id}{os.path.splitext(f)[1]}')
remove = remove_ids if delete_from_library else []
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
self.status_bar.show_message(_('Sending books to device.'), 5000)
diff --git a/src/calibre/gui2/dialogs/custom_recipes.py b/src/calibre/gui2/dialogs/custom_recipes.py
index 43a004c301..93d993f05d 100644
--- a/src/calibre/gui2/dialogs/custom_recipes.py
+++ b/src/calibre/gui2/dialogs/custom_recipes.py
@@ -725,7 +725,7 @@ class CustomRecipes(Dialog):
c = 0
while self.recipe_list.has_title(title):
c += 1
- title = '%s %d' % (base_title, c)
+ title = f'{base_title} {c}'
try:
src = options_to_recipe_source(title, oldest_article, max_articles_per_feed, group.feeds)
compile_recipe(src)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 1c65c6379c..0e5735b692 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -114,7 +114,7 @@ class MyBlockingBusy(QDialog): # {{{
if args.series_map_rules:
self.selected_options += 1
if DEBUG:
- print('Number of steps for bulk metadata: %d' % self.selected_options)
+ print(f'Number of steps for bulk metadata: {self.selected_options}')
print('Optionslist: ')
print(options)
@@ -805,13 +805,13 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.testgrid.addWidget(w, i+offset, 0, 1, 1)
w = QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True)
- name = 'book_%d_text'%i
+ name = f'book_{i}_text'
setattr(self, name, w)
self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 1, 1, 1)
w = QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True)
- name = 'book_%d_result'%i
+ name = f'book_{i}_result'
setattr(self, name, w)
self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 2, 1, 1)
@@ -991,7 +991,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.s_r_src_ident.setVisible(True)
for i in range(self.s_r_number_of_books):
- w = getattr(self, 'book_%d_text'%(i+1))
+ w = getattr(self, f'book_{i + 1}_text')
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
src = self.s_r_sf_itemdata(idx)
t = self.s_r_get_field(mi, src)
@@ -1061,7 +1061,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.test_result.setText(tt)
update_status_actions(self.test_result, self.s_r_error is None, tt)
for i in range(self.s_r_number_of_books):
- getattr(self, 'book_%d_result'%(i+1)).setText('')
+ getattr(self, f'book_{i + 1}_result').setText('')
def s_r_func(self, match):
rfunc = self.s_r_functions[str(self.replace_func.currentText())]
@@ -1186,7 +1186,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
for i in range(self.s_r_number_of_books):
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
- wr = getattr(self, 'book_%d_result'%(i+1))
+ wr = getattr(self, f'book_{i + 1}_result')
try:
result = self.s_r_do_regexp(mi)
t = self.s_r_do_destination(mi, result)
diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py
index c47ca84b9f..30f6650552 100644
--- a/src/calibre/gui2/email.py
+++ b/src/calibre/gui2/email.py
@@ -76,8 +76,7 @@ class Sendmail:
try_count = 0
while True:
if try_count > 0:
- log('\nRetrying in %d seconds...\n' %
- self.rate_limit)
+ log(f'\nRetrying in {self.rate_limit} seconds...\n')
worker = Worker(self.sendmail,
(attachment, aname, to, subject, text, log))
worker.start()
diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py
index 18658b0372..1bca58d9a1 100644
--- a/src/calibre/gui2/jobs.py
+++ b/src/calibre/gui2/jobs.py
@@ -436,7 +436,7 @@ class ProgressBarDelegate(QAbstractItemDelegate): # {{{
except (TypeError, ValueError):
percent = 0
opts.progress = percent
- opts.text = (_('Unavailable') if percent == 0 else '%d%%'%percent)
+ opts.text = (_('Unavailable') if percent == 0 else f'{percent}%')
QApplication.style().drawControl(QStyle.ControlElement.CE_ProgressBar, opts, painter)
# }}}
diff --git a/src/calibre/gui2/keyboard.py b/src/calibre/gui2/keyboard.py
index c98fd68f48..1c7ebd3f57 100644
--- a/src/calibre/gui2/keyboard.py
+++ b/src/calibre/gui2/keyboard.py
@@ -442,16 +442,16 @@ class Editor(QFrame): # {{{
la = QLabel(text)
la.setStyleSheet('QLabel { margin-left: 1.5em }')
l.addWidget(la, off+which, 0, 1, 3)
- setattr(self, 'label%d'%which, la)
+ setattr(self, f'label{which}', la)
button = QPushButton(_('None'), self)
button.setObjectName(_('None'))
button.clicked.connect(partial(self.capture_clicked, which=which))
button.installEventFilter(self)
- setattr(self, 'button%d'%which, button)
+ setattr(self, f'button{which}', button)
clear = QToolButton(self)
clear.setIcon(QIcon.ic('clear_left.png'))
clear.clicked.connect(partial(self.clear_clicked, which=which))
- setattr(self, 'clear%d'%which, clear)
+ setattr(self, f'clear{which}', clear)
l.addWidget(button, off+which, 1, 1, 1)
l.addWidget(clear, off+which, 2, 1, 1)
la.setBuddy(button)
@@ -488,7 +488,7 @@ class Editor(QFrame): # {{{
else:
self.use_custom.setChecked(True)
for key, which in zip(self.current_keys, [1,2]):
- button = getattr(self, 'button%d'%which)
+ button = getattr(self, f'button{which}')
ns = key.toString(QKeySequence.SequenceFormat.NativeText)
button.setText(ns.replace('&', '&&'))
button.setObjectName(ns)
@@ -500,13 +500,13 @@ class Editor(QFrame): # {{{
def capture_clicked(self, which=1):
self.capture = which
- button = getattr(self, 'button%d'%which)
+ button = getattr(self, f'button{which}')
button.setText(_('Press a key...'))
button.setFocus(Qt.FocusReason.OtherFocusReason)
button.setStyleSheet('QPushButton { font-weight: bold}')
def clear_clicked(self, which=0):
- button = getattr(self, 'button%d'%which)
+ button = getattr(self, f'button{which}')
button.setText(_('None'))
button.setObjectName(_('None'))
@@ -529,7 +529,7 @@ class Editor(QFrame): # {{{
return QWidget.keyPressEvent(self, ev)
ev.accept()
- button = getattr(self, 'button%d'%which)
+ button = getattr(self, f'button{which}')
button.setStyleSheet('QPushButton { font-weight: normal}')
ns = sequence.toString(QKeySequence.SequenceFormat.NativeText)
button.setText(ns.replace('&', '&&'))
@@ -556,7 +556,7 @@ class Editor(QFrame): # {{{
return None
ans = []
for which in (1, 2):
- button = getattr(self, 'button%d'%which)
+ button = getattr(self, f'button{which}')
t = button.objectName()
if not t or t == _('None'):
continue
diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py
index eee796714b..bc69e5c92b 100644
--- a/src/calibre/gui2/library/alternate_views.py
+++ b/src/calibre/gui2/library/alternate_views.py
@@ -540,7 +540,7 @@ class CoverDelegate(QStyledItemDelegate):
ans = None
if mi is None:
mi = db.get_proxy_metadata(book_id)
- ans = formatter.safe_format(rule, mi, '', mi, column_name='cover_grid%d' % rule_index, template_cache=template_cache) or None
+ ans = formatter.safe_format(rule, mi, '', mi, column_name=f'cover_grid{rule_index}', template_cache=template_cache) or None
cache[book_id][rule] = ans
return ans, mi
diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py
index 4104f738ef..1cd3b02e76 100644
--- a/src/calibre/gui2/lrf_renderer/main.py
+++ b/src/calibre/gui2/lrf_renderer/main.py
@@ -188,7 +188,7 @@ class Main(MainWindow, Ui_MainWindow):
self.graphics_view.show()
self.spin_box.setRange(1, self.document.num_of_pages)
self.slider.setRange(1, self.document.num_of_pages)
- self.spin_box.setSuffix(' of %d'%(self.document.num_of_pages,))
+ self.spin_box.setSuffix(f' of {self.document.num_of_pages}')
self.spin_box.updateGeometry()
self.stack.setCurrentIndex(0)
self.graphics_view.setFocus(Qt.FocusReason.OtherFocusReason)
diff --git a/src/calibre/gui2/metadata/bulk_download.py b/src/calibre/gui2/metadata/bulk_download.py
index efabc7f70e..c19173eacd 100644
--- a/src/calibre/gui2/metadata/bulk_download.py
+++ b/src/calibre/gui2/metadata/bulk_download.py
@@ -264,7 +264,7 @@ def download(all_ids, tf, db, do_identify, covers, ensure_fields,
failed_covers = failed_covers.union(fcovs)
ans = ans.union(set(ids) - fids)
for book_id in ids:
- lp = os.path.join(tdir, '%d.log'%book_id)
+ lp = os.path.join(tdir, f'{book_id}.log')
if os.path.exists(lp):
with open(tf, 'ab') as dest, open(lp, 'rb') as src:
dest.write(('\n'+'#'*20 + f' Log for {title_map[book_id]} ' +
diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py
index 3458a63232..39ea189c10 100644
--- a/src/calibre/gui2/metadata/diff.py
+++ b/src/calibre/gui2/metadata/diff.py
@@ -419,7 +419,7 @@ class CoverView(QWidget):
f = p.font()
f.setBold(True)
p.setFont(f)
- sz = '\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
+ sz = f'\xa0{self.pixmap.width()} x {self.pixmap.height()}\xa0'
flags = int(Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight|Qt.TextFlag.TextSingleLine)
szrect = p.boundingRect(sztgt, flags, sz)
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index 564bf5836c..877fc25ccb 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -423,7 +423,7 @@ class IdentifyWorker(Thread): # {{{
m1.has_cached_cover_url = True
m2.has_cached_cover_url = False
m1.comments = 'Some comments '*10
- m1.tags = ['tag%d'%i for i in range(20)]
+ m1.tags = [f'tag{i}' for i in range(20)]
m1.rating = 4.4
m1.language = 'en'
m2.language = 'fr'
@@ -679,7 +679,7 @@ class CoversModel(QAbstractListModel): # {{{
self.beginResetModel(), self.endResetModel()
def get_item(self, src, pmap, waiting=False):
- sz = '%dx%d'%(pmap.width(), pmap.height())
+ sz = f'{pmap.width()}x{pmap.height()}'
text = (src + '\n' + sz)
scaled = pmap.scaled(
int(CoverDelegate.ICON_SIZE[0] * pmap.devicePixelRatio()), int(CoverDelegate.ICON_SIZE[1] * pmap.devicePixelRatio()),
diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py
index 44498e0535..c51a8cdd9f 100644
--- a/src/calibre/gui2/preferences/create_custom_column.py
+++ b/src/calibre/gui2/preferences/create_custom_column.py
@@ -978,7 +978,7 @@ class CreateNewCustomColumn:
if not generate_unused_lookup_name:
return (self.Result.DUPLICATE_KEY, _('The custom column %s already exists') % lookup_name)
for suffix_number in range(suffix_number, 100000):
- nk = '%s_%d'%(lookup_name, suffix_number)
+ nk = f'{lookup_name}_{suffix_number}'
if nk not in self.custcols:
lookup_name = nk
break
@@ -989,7 +989,7 @@ class CreateNewCustomColumn:
return (self.Result.DUPLICATE_HEADING,
_('The column heading %s already exists') % column_heading)
for i in range(suffix_number, 100000):
- nh = '%s_%d'%(column_heading, i)
+ nh = f'{column_heading}_{i}'
if nh not in headings:
column_heading = nh
break
diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py
index 34cab9bad8..f870ecfdf7 100644
--- a/src/calibre/gui2/preferences/look_feel.py
+++ b/src/calibre/gui2/preferences/look_feel.py
@@ -816,7 +816,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
name = str(fi.family())
self.font_display.setFont(font)
- self.font_display.setText(name + ' [%dpt]'%fi.pointSize())
+ self.font_display.setText(name + f' [{fi.pointSize()}pt]')
def change_font(self, *args):
fd = QFontDialog(self.build_font_obj(), self)
diff --git a/src/calibre/gui2/save.py b/src/calibre/gui2/save.py
index 043eb60a7e..d725c8aa96 100644
--- a/src/calibre/gui2/save.py
+++ b/src/calibre/gui2/save.py
@@ -42,7 +42,7 @@ def ensure_unique_components(data): # {{{
for book_ids in itervalues(cmap):
if len(book_ids) > 1:
for i, book_id in enumerate(sorted(book_ids)[1:]):
- suffix = ' (%d)' % (i + 1)
+ suffix = f' ({i + 1})'
components = bid_map[book_id]
components[-1] = components[-1] + suffix
# }}}
@@ -124,7 +124,7 @@ class Saver(QObject):
try:
ans = BookId(self.db.field_for('title', book_id), self.db.field_for('authors', book_id))
except Exception:
- ans = BookId((_('Unknown') + ' (%d)' % book_id), (_('Unknown'),))
+ ans = BookId((_('Unknown') + f' ({book_id})'), (_('Unknown'),))
self._book_id_data[book_id] = ans
return ans
@@ -251,7 +251,7 @@ class Saver(QObject):
fname = base_path + os.extsep + 'jpg'
mi.cover = os.path.basename(fname)
elif self.opts.update_metadata:
- fname = os.path.join(self.tdir, '%d.jpg' % book_id)
+ fname = os.path.join(self.tdir, f'{book_id}.jpg')
if fname:
with open(fname, 'wb') as f:
@@ -263,7 +263,7 @@ class Saver(QObject):
if self.opts.write_opf:
fname = base_path + os.extsep + 'opf'
elif self.opts.update_metadata:
- fname = os.path.join(self.tdir, '%d.opf' % book_id)
+ fname = os.path.join(self.tdir, f'{book_id}.opf')
if fname:
opf = metadata_to_opf(mi)
with open(fname, 'wb') as f:
diff --git a/src/calibre/gui2/store/loader.py b/src/calibre/gui2/store/loader.py
index 0ff159a073..152a14d68f 100644
--- a/src/calibre/gui2/store/loader.py
+++ b/src/calibre/gui2/store/loader.py
@@ -200,4 +200,4 @@ if __name__ == '__main__':
print(name)
print(code.encode('utf-8'))
print('\n', '_'*80, '\n', sep='')
- print('Time to download all %d plugins: %.2f seconds'%(count, time.time() - st))
+ print(f'Time to download all {count} plugins: {time.time() - st:.2f} seconds')
diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py
index 9870041034..3c98da821f 100644
--- a/src/calibre/gui2/toc/main.py
+++ b/src/calibre/gui2/toc/main.py
@@ -74,7 +74,7 @@ class XPathDialog(QDialog): # {{{
l.addWidget(la)
self.widgets = []
for i in range(5):
- la = _('Level %s ToC:')%('&%d'%(i+1))
+ la = _('Level %s ToC:')%(f'&{i + 1}')
xp = XPathEdit(self)
xp.set_msg(la)
self.widgets.append(xp)
@@ -363,10 +363,10 @@ class ItemView(QStackedWidget): # {{{
self.prefs.set('toc_from_headings_prefer_title', d.prefer_title_cb.isChecked())
def create_from_major_headings(self):
- self.headings_question(['//h:h%d'%i for i in range(1, 4)])
+ self.headings_question([f'//h:h{i}' for i in range(1, 4)])
def create_from_all_headings(self):
- self.headings_question(['//h:h%d'%i for i in range(1, 7)])
+ self.headings_question([f'//h:h{i}' for i in range(1, 7)])
def create_from_user_xpath(self):
d = XPathDialog(self, self.prefs)
diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py
index 50fb5aa3f6..da9662e70c 100644
--- a/src/calibre/gui2/tweak_book/boss.py
+++ b/src/calibre/gui2/tweak_book/boss.py
@@ -273,7 +273,7 @@ class Boss(QObject):
def mkdtemp(self, prefix=''):
self.container_count += 1
- return tempfile.mkdtemp(prefix='%s%05d-' % (prefix, self.container_count), dir=self.tdir)
+ return tempfile.mkdtemp(prefix=f'{prefix}{self.container_count:05}-', dir=self.tdir)
def _check_before_open(self):
if self.gui.action_save.isEnabled():
@@ -585,7 +585,7 @@ class Boss(QObject):
while c.exists(name) or c.manifest_has_name(name) or c.has_name_case_insensitive(name):
i += 1
name, ext = name.rpartition('.')[0::2]
- name = '%s_%d.%s' % (name, i, ext)
+ name = f'{name}_{i}.{ext}'
try:
with open(path, 'rb') as f:
c.add_file(name, f.read())
diff --git a/src/calibre/gui2/tweak_book/check.py b/src/calibre/gui2/tweak_book/check.py
index 7864e54a7b..c674c323d3 100644
--- a/src/calibre/gui2/tweak_book/check.py
+++ b/src/calibre/gui2/tweak_book/check.py
@@ -197,8 +197,7 @@ class Check(QSplitter):
ifix = ''
loc = loc_to_string(err.line, err.col)
if err.INDIVIDUAL_FIX:
- ifix = '%s
' % (
- self.items.currentRow(), _('Try to fix only this error'), err.INDIVIDUAL_FIX)
+ ifix = f"{err.INDIVIDUAL_FIX}
"
open_tt = _('Click to open in editor')
fix_tt = _('Try to fix all fixable errors automatically. Only works for some types of error.')
fix_msg = _('Try to correct all fixable errors automatically')
@@ -210,8 +209,7 @@ class Check(QSplitter):
if err.has_multiple_locations:
activate = []
for i, (name, lnum, col) in enumerate(err.all_locations):
- activate.append('%s %s' % (
- i, open_tt, name, loc_to_string(lnum, col)))
+ activate.append(f'{name} {loc_to_string(lnum, col)}')
many = len(activate) > 2
activate = '{}
'.format('
'.join(activate))
if many:
diff --git a/src/calibre/gui2/tweak_book/editor/help.py b/src/calibre/gui2/tweak_book/editor/help.py
index a096a9eccb..13aa5597da 100644
--- a/src/calibre/gui2/tweak_book/editor/help.py
+++ b/src/calibre/gui2/tweak_book/editor/help.py
@@ -89,12 +89,12 @@ def get_opf2_tag_index():
base = 'http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#'
ans = {}
for i, tag in enumerate(('package', 'metadata', 'manifest', 'spine', 'tours', 'guide')):
- ans[tag] = base + 'Section2.%d' % (i + 1)
+ ans[tag] = base + f'Section2.{i + 1}'
for i, tag in enumerate((
'title', 'creator', 'subject', 'description', 'publisher',
'contributor', 'date', 'type', 'format', 'identifier', 'source',
'language', 'relation', 'coverage', 'rights')):
- ans[tag] = base + 'Section2.2.%d' % (i + 1)
+ ans[tag] = base + f'Section2.2.{i + 1}'
ans['item'] = ans['manifest']
ans['itemref'] = ans['spine']
ans['reference'] = ans['guide']
diff --git a/src/calibre/gui2/tweak_book/editor/snippets.py b/src/calibre/gui2/tweak_book/editor/snippets.py
index 0ccc39dca8..8a91389c7b 100644
--- a/src/calibre/gui2/tweak_book/editor/snippets.py
+++ b/src/calibre/gui2/tweak_book/editor/snippets.py
@@ -159,8 +159,7 @@ class TabStop(str):
return self
def __repr__(self):
- return 'TabStop(text=%s num=%d start=%d is_mirror=%s takes_selection=%s is_toplevel=%s)' % (
- str.__repr__(self), self.num, self.start, self.is_mirror, self.takes_selection, self.is_toplevel)
+ return f'TabStop(text={str.__repr__(self)} num={self.num} start={self.start} is_mirror={self.is_mirror} takes_selection={self.takes_selection} is_toplevel={self.is_toplevel})'
def parse_template(template, start_offset=0, is_toplevel=True, grouped=True):
diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py
index 9551584c4c..1326dba6eb 100644
--- a/src/calibre/gui2/tweak_book/editor/text.py
+++ b/src/calibre/gui2/tweak_book/editor/text.py
@@ -908,9 +908,9 @@ class TextEdit(PlainTextEdit):
return
r, g, b, a = color.getRgb()
if a == 255:
- color = 'rgb(%d, %d, %d)' % (r, g, b)
+ color = f'rgb({r}, {g}, {b})'
else:
- color = 'rgba(%d, %d, %d, %.2g)' % (r, g, b, a/255)
+ color = f'rgba({r}, {g}, {b}, {a / 255:.2g})'
prefix, suffix = {
'bold': ('', ''),
'italic': ('', ''),
diff --git a/src/calibre/gui2/tweak_book/editor/themes.py b/src/calibre/gui2/tweak_book/editor/themes.py
index c2944fb384..36c3709a31 100644
--- a/src/calibre/gui2/tweak_book/editor/themes.py
+++ b/src/calibre/gui2/tweak_book/editor/themes.py
@@ -57,8 +57,8 @@ def default_theme():
# The solarized themes {{{
SLDX = {'base03':'1c1c1c', 'base02':'262626', 'base01':'585858', 'base00':'626262', 'base0':'808080', 'base1':'8a8a8a', 'base2':'e4e4e4', 'base3':'ffffd7', 'yellow':'af8700', 'orange':'d75f00', 'red':'d70000', 'magenta':'af005f', 'violet':'5f5faf', 'blue':'0087ff', 'cyan':'00afaf', 'green':'5f8700'} # noqa: E501
SLD = {'base03':'002b36', 'base02':'073642', 'base01':'586e75', 'base00':'657b83', 'base0':'839496', 'base1':'93a1a1', 'base2':'eee8d5', 'base3':'fdf6e3', 'yellow':'b58900', 'orange':'cb4b16', 'red':'dc322f', 'magenta':'d33682', 'violet':'6c71c4', 'blue':'268bd2', 'cyan':'2aa198', 'green':'859900'} # noqa: E501
-m = {'base%d'%n:'base%02d'%n for n in range(1, 4)}
-m.update({'base%02d'%n:'base%d'%n for n in range(1, 4)})
+m = {f'base{n}':f'base{n:02}' for n in range(1, 4)}
+m.update({f'base{n:02}':f'base{n}' for n in range(1, 4)})
SLL = {m.get(k, k): v for k, v in iteritems(SLD)}
SLLX = {m.get(k, k): v for k, v in iteritems(SLDX)}
SOLARIZED = \
diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py
index dd742fdb8c..0604c41095 100644
--- a/src/calibre/gui2/tweak_book/editor/widget.py
+++ b/src/calibre/gui2/tweak_book/editor/widget.py
@@ -112,7 +112,7 @@ def register_text_editor_actions(_reg, palette):
for i, name in enumerate(('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p')):
text = ('&' + name) if name == 'p' else (name[0] + '&' + name[1])
desc = _('Convert the paragraph to <%s>') % name
- ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, 'Ctrl+%d' % (i + 1), desc, syntaxes=())
+ ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, f'Ctrl+{i + 1}', desc, syntaxes=())
ac.setToolTip(desc)
for transform, text in [
@@ -423,7 +423,7 @@ class Editor(QMainWindow):
# For some unknown reason this button is occasionally a
# QPushButton instead of a QToolButton
ch.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
- for name in tuple('h%d' % d for d in range(1, 7)) + ('p',):
+ for name in tuple(f'h{d}' for d in range(1, 7)) + ('p',):
m.addAction(actions[f'rename-block-tag-{name}'])
for name in tprefs.get('editor_common_toolbar', ()):
diff --git a/src/calibre/gui2/tweak_book/reports.py b/src/calibre/gui2/tweak_book/reports.py
index 15bff2e9f2..f312700550 100644
--- a/src/calibre/gui2/tweak_book/reports.py
+++ b/src/calibre/gui2/tweak_book/reports.py
@@ -504,7 +504,7 @@ class ImagesModel(FileCollection):
if col == 2:
return str(len(entry.usage))
if col == 3:
- return '%d x %d' % (entry.width, entry.height)
+ return f'{entry.width} x {entry.height}'
elif role == Qt.ItemDataRole.UserRole:
try:
return self.files[index.row()]
@@ -1402,7 +1402,7 @@ class ReportsWidget(QWidget):
self.stack.widget(i)(data)
if DEBUG:
category = self.reports.item(i).data(Qt.ItemDataRole.DisplayRole)
- print('Widget time for %12s: %.2fs seconds' % (category, time.time() - st))
+ print(f'Widget time for {category:12}: {time.time() - st:.2f}s seconds')
def save(self):
save_state('splitter-state', bytearray(self.splitter.saveState()))
@@ -1504,7 +1504,7 @@ class Reports(Dialog):
data, timing = data
if DEBUG:
for x, t in sorted(iteritems(timing), key=itemgetter(1)):
- print('Time for %6s data: %.3f seconds' % (x, t))
+ print(f'Time for {x:6} data: {t:.3f} seconds')
self.reports(data)
def accept(self):
diff --git a/src/calibre/gui2/update.py b/src/calibre/gui2/update.py
index 4833079dc8..a34c82520e 100644
--- a/src/calibre/gui2/update.py
+++ b/src/calibre/gui2/update.py
@@ -219,7 +219,7 @@ class UpdateMixin:
green, _('Update available'), version_url, calibre_version, plt)
else:
plt = ngettext('plugin update available', 'plugin updates available', number_of_plugin_updates)
- msg = ('%d %s')%(version_url, number_of_plugin_updates, plt)
+ msg = f"{number_of_plugin_updates} {plt}"
self.status_bar.update_label.setText(msg)
self.status_bar.update_label.setVisible(True)
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 4ee39bcb59..2e7ec959ba 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -338,7 +338,7 @@ def draw_size(p, rect, w, h):
f = p.font()
f.setBold(True)
p.setFont(f)
- sz = '\u00a0%d x %d\u00a0'%(w, h)
+ sz = f'\xa0{w} x {h}\xa0'
flags = Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight|Qt.TextFlag.TextSingleLine
szrect = p.boundingRect(rect, flags, sz)
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py
index 24b16693ea..8b9b9ab692 100644
--- a/src/calibre/gui2/widgets2.py
+++ b/src/calibre/gui2/widgets2.py
@@ -382,7 +382,7 @@ class RatingEditor(QComboBox):
self.redo()
return ev.accept()
k = ev.key()
- num = {getattr(Qt, 'Key_%d'%i):i for i in range(6)}.get(k)
+ num = {getattr(Qt, f'Key_{i}'):i for i in range(6)}.get(k)
if num is None:
return QComboBox.keyPressEvent(self, ev)
ev.accept()
diff --git a/src/calibre/library/catalogs/bibtex.py b/src/calibre/library/catalogs/bibtex.py
index b58f91d06b..0406a6c760 100644
--- a/src/calibre/library/catalogs/bibtex.py
+++ b/src/calibre/library/catalogs/bibtex.py
@@ -390,7 +390,7 @@ class BIBTEX(CatalogPlugin):
if bib_entry == 'book':
nb_books = len(list(filter(check_entry_book_valid, data)))
if nb_books < nb_entries:
- log.warn('Only %d entries in %d are book compatible' % (nb_books, nb_entries))
+ log.warn(f'Only {nb_books} entries in {nb_entries} are book compatible')
nb_entries = nb_books
# If connected device, add 'On Device' values to data
diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py
index 948ea35bb2..684b5fbd8c 100644
--- a/src/calibre/library/catalogs/epub_mobi_builder.py
+++ b/src/calibre/library/catalogs/epub_mobi_builder.py
@@ -245,7 +245,7 @@ class CatalogBuilder:
index = book['series_index']
integer = int(index)
fraction = index - integer
- series_index = '%04d%s' % (integer, str(f'{fraction:0.4f}').lstrip('0'))
+ series_index = f"{integer:04}{str(f'{fraction:0.4f}').lstrip('0')}"
key = '{} ~{} {}'.format(self._kf_author_to_author_sort(book['author']),
self.generate_sort_title(book['series']),
series_index)
@@ -271,7 +271,7 @@ class CatalogBuilder:
index = book['series_index']
integer = int(index)
fraction = index - integer
- series_index = '%04d%s' % (integer, str(f'{fraction:0.4f}').lstrip('0'))
+ series_index = f"{integer:04}{str(f'{fraction:0.4f}').lstrip('0')}"
fs = '{:<%d}~{!s}{!s}' % longest_author_sort
key = fs.format(capitalize(book['author_sort']),
self.generate_sort_title(book['series']),
@@ -282,7 +282,7 @@ class CatalogBuilder:
index = book['series_index']
integer = int(index)
fraction = index - integer
- series_index = '%04d%s' % (integer, str(f'{fraction:0.4f}').lstrip('0'))
+ series_index = f"{integer:04}{str(f'{fraction:0.4f}').lstrip('0')}"
key = '{} {}'.format(self.generate_sort_title(book['series']),
series_index)
return key
@@ -382,8 +382,7 @@ class CatalogBuilder:
break
if self.opts.verbose:
self.opts.log(' Thumbnails:')
- self.opts.log(' DPI = %d; thumbnail dimensions: %d x %d' %
- (x.dpi, self.thumb_width, self.thumb_height))
+ self.opts.log(f' DPI = {x.dpi}; thumbnail dimensions: {self.thumb_width} x {self.thumb_height}')
def compute_total_steps(self):
''' Calculate number of build steps to generate catalog.
@@ -1843,9 +1842,9 @@ class CatalogBuilder:
for i, date in enumerate(self.DATE_RANGE):
date_range_limit = self.DATE_RANGE[i]
if i:
- date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i])
+ date_range = f'{self.DATE_RANGE[i - 1]} to {self.DATE_RANGE[i]} days ago'
else:
- date_range = 'Last %d days' % (self.DATE_RANGE[i])
+ date_range = f'Last {self.DATE_RANGE[i]} days'
for book in self.books_by_date_range:
book_time = book['timestamp']
@@ -2830,9 +2829,7 @@ class CatalogBuilder:
self.update_progress_full_step(_('Descriptions HTML'))
for title_num, title in enumerate(self.books_by_title):
- self.update_progress_micro_step('%s %d of %d' %
- (_('Description HTML'),
- title_num, len(self.books_by_title)),
+ self.update_progress_micro_step(f"{_('Description HTML')} {title_num} of {len(self.books_by_title)}",
float(title_num * 100 / len(self.books_by_title)) / 100)
# Generate the header from user-customizable template
@@ -3378,9 +3375,9 @@ class CatalogBuilder:
today_time = datetime.datetime(today.year, today.month, today.day)
for i, date in enumerate(self.DATE_RANGE):
if i:
- date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i])
+ date_range = f'{self.DATE_RANGE[i - 1]} to {self.DATE_RANGE[i]} days ago'
else:
- date_range = 'Last %d days' % (self.DATE_RANGE[i])
+ date_range = f'Last {self.DATE_RANGE[i]} days'
date_range_limit = self.DATE_RANGE[i]
for book in self.books_by_date_range:
book_time = datetime.datetime(book['timestamp'].year, book['timestamp'].month, book['timestamp'].day)
@@ -3400,8 +3397,8 @@ class CatalogBuilder:
sec_text = books_by_date_range[1]
content_src = '{}#bda_{}'.format(HTML_file,
books_by_date_range[1].replace(' ', ''))
- navStr = '%d titles' % books_by_date_range[2] if books_by_date_range[2] > 1 else \
- '%d title' % books_by_date_range[2]
+ navStr = f'{books_by_date_range[2]} titles' if books_by_date_range[2] > 1 else \
+ f'{books_by_date_range[2]} title'
cm_tags = {'description': books_by_date_range[0], 'author': navStr}
self.generate_ncx_subsection(navPointTag, sec_id, sec_text, content_src, cm_tags)
@@ -3435,8 +3432,8 @@ class CatalogBuilder:
sec_id = f'bda_{books_by_month[1].year}-{books_by_month[1].month}-ID'
sec_text = datestr
content_src = f'{HTML_file}#bda_{books_by_month[1].year}-{books_by_month[1].month}'
- navStr = '%d titles' % books_by_month[2] if books_by_month[2] > 1 else \
- '%d title' % books_by_month[2]
+ navStr = f'{books_by_month[2]} titles' if books_by_month[2] > 1 else \
+ f'{books_by_month[2]} title'
cm_tags = {'description': books_by_month[0], 'author': navStr}
self.generate_ncx_subsection(navPointTag, sec_id, sec_text, content_src, cm_tags)
@@ -3486,9 +3483,9 @@ class CatalogBuilder:
today_time = datetime.datetime(today.year, today.month, today.day)
for i, date in enumerate(self.DATE_RANGE):
if i:
- date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i])
+ date_range = f'{self.DATE_RANGE[i - 1]} to {self.DATE_RANGE[i]} days ago'
else:
- date_range = 'Last %d days' % (self.DATE_RANGE[i])
+ date_range = f'Last {self.DATE_RANGE[i]} days'
date_range_limit = self.DATE_RANGE[i]
for book in self.bookmarked_books_by_date_read:
bookmark_time = utcfromtimestamp(book['bookmark_timestamp'])
@@ -3533,8 +3530,8 @@ class CatalogBuilder:
sec_id = f'bdr_{books_by_day[1].year}-{books_by_day[1].month}-{books_by_day[1].day}ID'
sec_text = datestr
content_src = f'{HTML_file}#bdr_{books_by_day[1].year}-{books_by_day[1].month}-{books_by_day[1].day}'
- navStr = '%d titles' % books_by_day[2] if books_by_day[2] > 1 else \
- '%d title' % books_by_day[2]
+ navStr = f'{books_by_day[2]} titles' if books_by_day[2] > 1 else \
+ f'{books_by_day[2]} title'
cm_tags = {'description': books_by_day[0], 'author': navStr}
self.generate_ncx_subsection(navPointTag, sec_id, sec_text, content_src, cm_tags)
@@ -3919,8 +3916,7 @@ class CatalogBuilder:
image_dir = f'{self.catalog_path}/images'
for i, title in enumerate(self.books_by_title):
# Update status
- self.update_progress_micro_step('%s %d of %d' %
- (_('Thumbnail'), i, len(self.books_by_title)),
+ self.update_progress_micro_step(f"{_('Thumbnail')} {i} of {len(self.books_by_title)}",
i / float(len(self.books_by_title)))
thumb_file = 'thumbnail_{}.jpg'.format(int(title['id']))
@@ -3940,7 +3936,7 @@ class CatalogBuilder:
thumb_generated = False
if not thumb_generated:
- self.opts.log.warn(" using default cover for '%s' (%d)" % (title['title'], title['id']))
+ self.opts.log.warn(f" using default cover for '{title['title']}' ({title['id']})")
# Confirm thumb exists, default is current
default_thumb_fp = os.path.join(image_dir, 'thumbnail_default.jpg')
cover = os.path.join(self.catalog_path, 'DefaultCover.png')
diff --git a/src/calibre/library/catalogs/utils.py b/src/calibre/library/catalogs/utils.py
index e624d381da..4db3b1f967 100644
--- a/src/calibre/library/catalogs/utils.py
+++ b/src/calibre/library/catalogs/utils.py
@@ -72,7 +72,7 @@ class NumberToText: # {{{
elif hundredsComponent and tensComponent:
result = hundredsComponentString + ' ' + tensComponentString
else:
- prints(' NumberToText.stringFromInt(): empty result translating %d' % intToTranslate)
+ prints(f' NumberToText.stringFromInt(): empty result translating {intToTranslate}')
return result
def numberTranslate(self):
@@ -175,7 +175,7 @@ class NumberToText: # {{{
return
if number > 10**9:
- self.text = '%d out of range' % number
+ self.text = f'{number} out of range'
return
if number == 10**9:
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 3373038e07..c85dba0dc3 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -25,7 +25,7 @@ class CustomColumns:
'int', 'float', 'bool', 'series', 'composite', 'enumeration'])
def custom_table_names(self, num):
- return 'custom_column_%d'%num, 'books_custom_column_%d_link'%num
+ return f'custom_column_{num}', f'books_custom_column_{num}_link'
@property
def custom_tables(self):
diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index ccb797a52e..225ae9eec6 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -814,11 +814,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
i = 0
while True:
i += 1
- func = getattr(LibraryDatabase, 'upgrade_version%d'%i, None)
+ func = getattr(LibraryDatabase, f'upgrade_version{i}', None)
if func is None:
break
if self.user_version == i:
- print('Upgrading database from version: %d'%i)
+ print(f'Upgrading database from version: {i}')
func(self.conn)
def close(self):
@@ -1057,15 +1057,15 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
self.conn.get('SELECT id, name FROM series')]
def series_name(self, series_id):
- return self.conn.get('SELECT name FROM series WHERE id=%d'%series_id,
+ return self.conn.get(f'SELECT name FROM series WHERE id={series_id}',
all=False)
def author_name(self, author_id):
- return self.conn.get('SELECT name FROM authors WHERE id=%d'%author_id,
+ return self.conn.get(f'SELECT name FROM authors WHERE id={author_id}',
all=False)
def tag_name(self, tag_id):
- return self.conn.get('SELECT name FROM tags WHERE id=%d'%tag_id,
+ return self.conn.get(f'SELECT name FROM tags WHERE id={tag_id}',
all=False)
def all_authors(self):
@@ -1102,7 +1102,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
if len(ids) > 50000:
return True
if len(ids) == 1:
- ids = '(%d)'%ids[0]
+ ids = f'({ids[0]})'
else:
ids = repr(ids)
return self.conn.get(f'''
@@ -1385,7 +1385,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
yield from feeds
def get_feed(self, id):
- return self.conn.get('SELECT script FROM feeds WHERE id=%d'%id,
+ return self.conn.get(f'SELECT script FROM feeds WHERE id={id}',
all=False)
def update_feed(self, id, script, title):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 497de92981..7d27291265 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -622,7 +622,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
author = author[:-1]
if not author:
author = ascii_filename(_('Unknown'))
- path = author + '/' + title + ' (%d)'%id
+ path = author + '/' + title + f' ({id})'
return path
def construct_file_name(self, id):
@@ -994,7 +994,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
row = None
if row is None:
- raise ValueError('No book with id: %d'%idx)
+ raise ValueError(f'No book with id: {idx}')
fm = self.FIELD_MAP
mi = Metadata(None, template_cache=self.formatter_template_cache)
@@ -1318,7 +1318,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def format_hash(self, id_, fmt):
path = self.format_abspath(id_, fmt, index_is_id=True)
if path is None:
- raise NoSuchFormat('Record %d has no fmt: %s'%(id_, fmt))
+ raise NoSuchFormat(f'Record {id_} has no fmt: {fmt}')
sha = hashlib.sha256()
with open(path, 'rb') as f:
while True:
@@ -1339,7 +1339,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = self.format_abspath(index, fmt, index_is_id=index_is_id)
if path is None:
id_ = index if index_is_id else self.id(index)
- raise NoSuchFormat('Record %d has no format: %s'%(id_, fmt))
+ raise NoSuchFormat(f'Record {id_} has no format: {fmt}')
return path
def format_abspath(self, index, format, index_is_id=False):
@@ -1399,7 +1399,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = self.format_abspath(index, fmt, index_is_id=index_is_id)
if path is None:
id_ = index if index_is_id else self.id(index)
- raise NoSuchFormat('Record %d has no %s file'%(id_, fmt))
+ raise NoSuchFormat(f'Record {id_} has no {fmt} file')
if windows_atomic_move is not None:
if not isinstance(dest, string_or_bytes):
raise Exception('Error, you must pass the dest as a path when'
@@ -1758,8 +1758,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.id = id
def __unicode_representation__(self):
- return 'n=%s s=%s c=%d rt=%d rc=%d id=%s' % (
- self.n, self.s, self.c, self.rt, self.rc, self.id)
+ return f'n={self.n} s={self.s} c={self.c} rt={self.rt} rc={self.rc} id={self.id}'
__str__ = __unicode_representation__
@@ -3677,7 +3676,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def add_custom_book_data(self, book_id, name, val):
x = self.conn.get('SELECT id FROM books WHERE ID=?', (book_id,), all=False)
if x is None:
- raise ValueError('add_custom_book_data: no such book_id %d'%book_id)
+ raise ValueError(f'add_custom_book_data: no such book_id {book_id}')
# Do the json encode first, in case it throws an exception
s = json.dumps(val, default=to_json)
self.conn.execute('''INSERT OR REPLACE INTO books_plugin_data(book, name, val)
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index c782803133..f74008aba3 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -17,11 +17,11 @@ class SchemaUpgrade:
# Upgrade database
while True:
uv = self.user_version
- meth = getattr(self, 'upgrade_version_%d'%uv, None)
+ meth = getattr(self, f'upgrade_version_{uv}', None)
if meth is None:
break
else:
- print('Upgrading database to version %d...'%(uv+1))
+ print(f'Upgrading database to version {uv + 1}...')
meth()
self.user_version = uv+1
diff --git a/src/calibre/rpdb.py b/src/calibre/rpdb.py
index 8fc42c1906..c50fe73c94 100644
--- a/src/calibre/rpdb.py
+++ b/src/calibre/rpdb.py
@@ -29,7 +29,7 @@ class RemotePdb(pdb.Pdb):
self.sock.bind((addr, port))
self.sock.listen(1)
with suppress(OSError):
- print('pdb is running on %s:%d' % (addr, port), file=sys.stderr)
+ print(f'pdb is running on {addr}:{port}', file=sys.stderr)
clientsocket, address = self.sock.accept()
clientsocket.setblocking(True)
self.clientsocket = clientsocket
@@ -100,7 +100,7 @@ def set_trace(port=4444, skip=None):
def cli(port=4444):
- print('Connecting to remote debugger on port %d...' % port)
+ print(f'Connecting to remote debugger on port {port}...')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
for i in range(20):
try:
diff --git a/src/calibre/srv/auto_reload.py b/src/calibre/srv/auto_reload.py
index d2c7c2ca0d..3200be4597 100644
--- a/src/calibre/srv/auto_reload.py
+++ b/src/calibre/srv/auto_reload.py
@@ -284,7 +284,7 @@ class Worker:
if join_process(self.p) is None:
self.p.kill()
self.p.wait()
- self.log('Killed server process %d with return code: %d' % (self.p.pid, self.p.returncode))
+ self.log(f'Killed server process {self.p.pid} with return code: {self.p.returncode}')
self.p = None
def restart(self, forced=False):
diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py
index 20f5cb3b0d..44733abc30 100644
--- a/src/calibre/srv/content.py
+++ b/src/calibre/srv/content.py
@@ -283,7 +283,7 @@ def icon(ctx, rd, which):
except OSError:
raise HTTPNotFound()
with lock:
- cached = os.path.join(rd.tdir, 'icons', '%d-%s.png' % (sz, which))
+ cached = os.path.join(rd.tdir, 'icons', f'{sz}-{which}.png')
try:
return share_open(cached, 'rb')
except OSError:
diff --git a/src/calibre/srv/http_request.py b/src/calibre/srv/http_request.py
index 8ab7dd53d4..384a0613f1 100644
--- a/src/calibre/srv/http_request.py
+++ b/src/calibre/srv/http_request.py
@@ -310,8 +310,7 @@ class HTTPRequest(Connection):
request_content_length = int(inheaders.get('Content-Length', 0))
if request_content_length > self.max_request_body_size:
return self.simple_response(http_client.REQUEST_ENTITY_TOO_LARGE,
- 'The entity sent with the request exceeds the maximum '
- 'allowed bytes (%d).' % self.max_request_body_size)
+ f'The entity sent with the request exceeds the maximum allowed bytes ({self.max_request_body_size}).')
# Persistent connection support
if self.response_protocol is HTTP11:
# Both server and client are HTTP/1.1
@@ -374,7 +373,7 @@ class HTTPRequest(Connection):
return self.simple_response(http_client.BAD_REQUEST, f'{reprlib.repr(line.strip())} is not a valid chunk size')
if bytes_read[0] + chunk_size + 2 > self.max_request_body_size:
return self.simple_response(http_client.REQUEST_ENTITY_TOO_LARGE,
- 'Chunked request is larger than %d bytes' % self.max_request_body_size)
+ f'Chunked request is larger than {self.max_request_body_size} bytes')
if chunk_size == 0:
self.set_state(READ, self.read_chunk_separator, inheaders, Accumulator(), buf, bytes_read, last=True)
else:
@@ -395,7 +394,7 @@ class HTTPRequest(Connection):
bytes_read[0] += len(line)
if bytes_read[0] > self.max_request_body_size:
return self.simple_response(http_client.REQUEST_ENTITY_TOO_LARGE,
- 'Chunked request is larger than %d bytes' % self.max_request_body_size)
+ f'Chunked request is larger than {self.max_request_body_size} bytes')
if last:
self.prepare_response(inheaders, buf)
else:
diff --git a/src/calibre/srv/http_response.py b/src/calibre/srv/http_response.py
index 8d5d672d08..f9e2047dac 100644
--- a/src/calibre/srv/http_response.py
+++ b/src/calibre/srv/http_response.py
@@ -194,7 +194,7 @@ def compress_readable_output(src_file, compress_level=6):
def get_range_parts(ranges, content_type, content_length): # {{{
def part(r):
- ans = [f'--{MULTIPART_SEPARATOR}', 'Content-Range: bytes %d-%d/%d' % (r.start, r.stop, content_length)]
+ ans = [f'--{MULTIPART_SEPARATOR}', f'Content-Range: bytes {r.start}-{r.stop}/{content_length}']
if content_type:
ans.append(f'Content-Type: {content_type}')
ans.append('')
@@ -418,7 +418,7 @@ class HTTPConnection(HTTPRequest):
msg = msg.encode('utf-8')
ct = 'http' if self.method == 'TRACE' else 'plain'
buf = [
- '%s %d %s' % (self.response_protocol, status_code, http_client.responses[status_code]),
+ f'{self.response_protocol} {status_code} {http_client.responses[status_code]}',
f'Content-Length: {len(msg)}',
f'Content-Type: text/{ct}; charset=UTF-8',
'Date: ' + http_date(),
@@ -456,12 +456,9 @@ class HTTPConnection(HTTPRequest):
def send_range_not_satisfiable(self, content_length):
buf = [
- '%s %d %s' % (
- self.response_protocol,
- http_client.REQUESTED_RANGE_NOT_SATISFIABLE,
- http_client.responses[http_client.REQUESTED_RANGE_NOT_SATISFIABLE]),
+ f'{self.response_protocol} {http_client.REQUESTED_RANGE_NOT_SATISFIABLE} {http_client.responses[http_client.REQUESTED_RANGE_NOT_SATISFIABLE]}',
'Date: ' + http_date(),
- 'Content-Range: bytes */%d' % content_length,
+ f'Content-Range: bytes */{content_length}',
]
response_data = header_list_to_file(buf)
self.log_access(status_code=http_client.REQUESTED_RANGE_NOT_SATISFIABLE, response_size=response_data.sz)
@@ -469,7 +466,7 @@ class HTTPConnection(HTTPRequest):
def send_not_modified(self, etag=None):
buf = [
- '%s %d %s' % (self.response_protocol, http_client.NOT_MODIFIED, http_client.responses[http_client.NOT_MODIFIED]),
+ f'{self.response_protocol} {http_client.NOT_MODIFIED} {http_client.responses[http_client.NOT_MODIFIED]}',
'Content-Length: 0',
'Date: ' + http_date(),
]
@@ -519,7 +516,7 @@ class HTTPConnection(HTTPRequest):
if ct.startswith('text/') and 'charset=' not in ct:
outheaders.set('Content-Type', ct + '; charset=UTF-8', replace_all=True)
- buf = [HTTP11 + (' %d ' % data.status_code) + http_client.responses[data.status_code]]
+ buf = [HTTP11 + f' {data.status_code} ' + http_client.responses[data.status_code]]
for header, value in sorted(iteritems(outheaders), key=itemgetter(0)):
buf.append(f'{header}: {value}')
for morsel in itervalues(data.outcookie):
@@ -710,10 +707,10 @@ class HTTPConnection(HTTPRequest):
if compressible and not ranges:
outheaders.set('Content-Encoding', 'gzip', replace_all=True)
if getattr(output, 'content_length', None):
- outheaders.set('Calibre-Uncompressed-Length', '%d' % output.content_length)
+ outheaders.set('Calibre-Uncompressed-Length', f'{output.content_length}')
output = GeneratedOutput(compress_readable_output(output.src_file), etag=output.etag)
if output.content_length is not None and not compressible and not ranges:
- outheaders.set('Content-Length', '%d' % output.content_length, replace_all=True)
+ outheaders.set('Content-Length', f'{output.content_length}', replace_all=True)
if compressible or output.content_length is None:
outheaders.set('Transfer-Encoding', 'chunked', replace_all=True)
@@ -721,13 +718,13 @@ class HTTPConnection(HTTPRequest):
if ranges:
if len(ranges) == 1:
r = ranges[0]
- outheaders.set('Content-Length', '%d' % r.size, replace_all=True)
- outheaders.set('Content-Range', 'bytes %d-%d/%d' % (r.start, r.stop, output.content_length), replace_all=True)
+ outheaders.set('Content-Length', f'{r.size}', replace_all=True)
+ outheaders.set('Content-Range', f'bytes {r.start}-{r.stop}/{output.content_length}', replace_all=True)
output.ranges = r
else:
range_parts = get_range_parts(ranges, outheaders.get('Content-Type'), output.content_length)
size = sum(map(len, range_parts)) + sum(r.size + 4 for r in ranges)
- outheaders.set('Content-Length', '%d' % size, replace_all=True)
+ outheaders.set('Content-Length', f'{size}', replace_all=True)
outheaders.set('Content-Type', 'multipart/byteranges; boundary=' + MULTIPART_SEPARATOR, replace_all=True)
output.ranges = zip_longest(ranges, range_parts)
request.status_code = http_client.PARTIAL_CONTENT
diff --git a/src/calibre/srv/legacy.py b/src/calibre/srv/legacy.py
index b89e9e27a0..283ec1df68 100644
--- a/src/calibre/srv/legacy.py
+++ b/src/calibre/srv/legacy.py
@@ -101,18 +101,18 @@ def build_search_box(num, search, sort, order, ctx, field_metadata, library_id):
def build_navigation(start, num, total, url_base): # {{{
end = min((start+num-1), total)
- tagline = E.span('Books %d to %d of %d'%(start, end, total),
+ tagline = E.span(f'Books {start} to {end} of {total}',
style='display: block; text-align: center;')
left_buttons = E.td(class_='button', style='text-align:left')
right_buttons = E.td(class_='button', style='text-align:right')
if start > 1:
for t,s in [('First', 1), ('Previous', max(start-num, 1))]:
- left_buttons.append(E.a(t, href='%s&start=%d'%(url_base, s)))
+ left_buttons.append(E.a(t, href=f'{url_base}&start={s}'))
if total > start + num:
for t,s in [('Next', start+num), ('Last', total-num+1)]:
- right_buttons.append(E.a(t, href='%s&start=%d'%(url_base, s)))
+ right_buttons.append(E.a(t, href=f'{url_base}&start={s}'))
buttons = E.table(
E.tr(left_buttons, right_buttons),
diff --git a/src/calibre/srv/library_broker.py b/src/calibre/srv/library_broker.py
index 5bfef2701b..3a8edc4b19 100644
--- a/src/calibre/srv/library_broker.py
+++ b/src/calibre/srv/library_broker.py
@@ -59,7 +59,7 @@ def make_library_id_unique(library_id, existing):
c = 0
while library_id in existing:
c += 1
- library_id = bname + ('%d' % c)
+ library_id = bname + f'{c}'
return library_id
diff --git a/src/calibre/srv/manage_users_cli.py b/src/calibre/srv/manage_users_cli.py
index 5e09aa35ba..13b16b0f64 100644
--- a/src/calibre/srv/manage_users_cli.py
+++ b/src/calibre/srv/manage_users_cli.py
@@ -213,13 +213,12 @@ def manage_users_cli(path=None, args=()):
question=_('What do you want to do?'), choices=(), default=None, banner=''):
prints(banner)
for i, choice in enumerate(choices):
- prints('%d)' % (i + 1), choice)
+ prints(f'{i + 1})', choice)
print()
while True:
prompt = question + f' [1-{len(choices)}]:'
if default is not None:
- prompt = question + ' [1-%d %s: %d]' % (
- len(choices), _('default'), default + 1)
+ prompt = question + f" [1-{len(choices)} {_('default')}: {default + 1}]"
reply = get_input(prompt)
if not reply and default is not None:
reply = str(default + 1)
diff --git a/src/calibre/srv/opds.py b/src/calibre/srv/opds.py
index b28b6a4465..38cdbf00cd 100644
--- a/src/calibre/srv/opds.py
+++ b/src/calibre/srv/opds.py
@@ -313,13 +313,13 @@ class NavFeed(Feed):
def __init__(self, id_, updated, request_context, offsets, page_url, up_url, title=None):
kwargs = {'up_link': up_url}
kwargs['first_link'] = page_url
- kwargs['last_link'] = page_url+'&offset=%d'%offsets.last_offset
+ kwargs['last_link'] = page_url+f'&offset={offsets.last_offset}'
if offsets.offset > 0:
kwargs['previous_link'] = \
- page_url+'&offset=%d'%offsets.previous_offset
+ page_url+f'&offset={offsets.previous_offset}'
if offsets.next_offset > -1:
kwargs['next_link'] = \
- page_url+'&offset=%d'%offsets.next_offset
+ page_url+f'&offset={offsets.next_offset}'
if title:
kwargs['title'] = title
Feed.__init__(self, id_, updated, request_context, **kwargs)
diff --git a/src/calibre/srv/pool.py b/src/calibre/srv/pool.py
index e1caa505c7..b950ae613b 100644
--- a/src/calibre/srv/pool.py
+++ b/src/calibre/srv/pool.py
@@ -20,7 +20,7 @@ class Worker(Thread):
self.notify_server = notify_server
self.log = log
self.working = False
- Thread.__init__(self, name='ServerWorker%d' % num)
+ Thread.__init__(self, name=f'ServerWorker{num}')
def run(self):
while True:
diff --git a/src/calibre/srv/routes.py b/src/calibre/srv/routes.py
index 73977bca3b..6cf85dbc13 100644
--- a/src/calibre/srv/routes.py
+++ b/src/calibre/srv/routes.py
@@ -164,7 +164,7 @@ class Route:
self.required_names = self.all_names - frozenset(self.defaults)
argspec = inspect.getfullargspec(self.endpoint)
if len(self.names) + 2 != len(argspec.args) - len(argspec.defaults or ()):
- raise route_error('Function must take %d non-default arguments' % (len(self.names) + 2))
+ raise route_error(f'Function must take {len(self.names) + 2} non-default arguments')
if argspec.args[2:len(self.names)+2] != self.names:
raise route_error("Function's argument names do not match the variable names in the route")
if not frozenset(self.type_checkers).issubset(frozenset(self.names)):
@@ -331,14 +331,14 @@ class Router:
outheaders['Pragma'] = 'no-cache'
elif isinstance(cc, numbers.Number):
cc = int(60 * 60 * cc)
- outheaders['Cache-Control'] = 'public, max-age=%d' % cc
+ outheaders['Cache-Control'] = f'public, max-age={cc}'
if cc == 0:
cc -= 100000
outheaders['Expires'] = http_date(cc + time.time())
else:
ctype, max_age = cc
max_age = int(60 * 60 * max_age)
- outheaders['Cache-Control'] = '%s, max-age=%d' % (ctype, max_age)
+ outheaders['Cache-Control'] = f'{ctype}, max-age={max_age}'
if max_age == 0:
max_age -= 100000
outheaders['Expires'] = http_date(max_age + time.time())
diff --git a/src/calibre/srv/tests/auth.py b/src/calibre/srv/tests/auth.py
index b119354d3b..3a7bed75b7 100644
--- a/src/calibre/srv/tests/auth.py
+++ b/src/calibre/srv/tests/auth.py
@@ -55,7 +55,7 @@ def router(prefer_basic_auth=False, ban_for=0, ban_after=5):
def urlopen(server, path='/closed', un='testuser', pw='testpw', method='digest'):
auth_handler = HTTPBasicAuthHandler() if method == 'basic' else HTTPDigestAuthHandler()
- url = 'http://localhost:%d%s' % (server.address[1], path)
+ url = f'http://localhost:{server.address[1]}{path}'
auth_handler.add_password(realm=REALM, uri=url, user=un, passwd=pw)
return build_opener(auth_handler).open(url)
@@ -147,15 +147,15 @@ class TestAuth(BaseTest):
return lmap, defaultlib
self.assertEqual(get_library(), 'l1')
- self.assertEqual(library_info()[0], {'l%d'%i:'l%d'%i for i in range(1, 4)})
+ self.assertEqual(library_info()[0], {f'l{i}':f'l{i}' for i in range(1, 4)})
self.assertEqual(library_info()[1], 'l1')
self.assertRaises(HTTPForbidden, get_library, 'xxx')
um.add_user('a', 'a')
- self.assertEqual(library_info('a')[0], {'l%d'%i:'l%d'%i for i in range(1, 4)})
+ self.assertEqual(library_info('a')[0], {f'l{i}':f'l{i}' for i in range(1, 4)})
um.update_user_restrictions('a', {'blocked_library_names': ['L2']})
- self.assertEqual(library_info('a')[0], {'l%d'%i:'l%d'%i for i in range(1, 4) if i != 2})
+ self.assertEqual(library_info('a')[0], {f'l{i}':f'l{i}' for i in range(1, 4) if i != 2})
um.update_user_restrictions('a', {'allowed_library_names': ['l3']})
- self.assertEqual(library_info('a')[0], {'l%d'%i:'l%d'%i for i in range(1, 4) if i == 3})
+ self.assertEqual(library_info('a')[0], {f'l{i}':f'l{i}' for i in range(1, 4) if i == 3})
self.assertEqual(library_info('a')[1], 'l3')
self.assertRaises(HTTPForbidden, get_library, 'a', 'l1')
self.assertRaises(HTTPForbidden, get_library, 'xxx')
@@ -236,7 +236,7 @@ class TestAuth(BaseTest):
curl = shutil.which('curl')
if curl and not (is_ci and ismacos): # curl mysteriously returns b'' in CI with no errors
def docurl(data, *args):
- cmd = [curl, '--silent'] + list(args) + ['http://localhost:%d/closed' % server.address[1]]
+ cmd = [curl, '--silent'] + list(args) + [f'http://localhost:{server.address[1]}/closed']
p = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
p.wait()
@@ -283,7 +283,7 @@ class TestAuth(BaseTest):
self.ae(r.status, http_client.UNAUTHORIZED)
auth_handler = HTTPDigestAuthHandler()
- url = 'http://localhost:%d%s' % (server.address[1], '/android')
+ url = f'http://localhost:{server.address[1]}/android'
auth_handler.add_password(realm=REALM, uri=url, user='testuser', passwd='testpw')
cj = CookieJar()
cookie_handler = HTTPCookieProcessor(cj)
diff --git a/src/calibre/srv/utils.py b/src/calibre/srv/utils.py
index 7b4f618eb5..730234eae6 100644
--- a/src/calibre/srv/utils.py
+++ b/src/calibre/srv/utils.py
@@ -316,9 +316,9 @@ class RotatingStream:
return
self.stream.close()
for i in range(self.history - 1, 0, -1):
- src, dest = '%s.%d' % (self.filename, i), '%s.%d' % (self.filename, i+1)
+ src, dest = f'{self.filename}.{i}', f'{self.filename}.{i + 1}'
self.rename(src, dest)
- self.rename(self.filename, '%s.%d' % (self.filename, 1))
+ self.rename(self.filename, f'{self.filename}.{1}')
self.set_output()
def clear(self):
diff --git a/src/calibre/translations/msgfmt.py b/src/calibre/translations/msgfmt.py
index ab63ef5b84..763d2aba0b 100644
--- a/src/calibre/translations/msgfmt.py
+++ b/src/calibre/translations/msgfmt.py
@@ -185,7 +185,7 @@ def make(filename, outfile):
# This is a message with plural forms
elif l.startswith('msgid_plural'):
if section != ID:
- print('msgid_plural not preceded by msgid on %s:%d' % (infile, lno),
+ print(f'msgid_plural not preceded by msgid on {infile}:{lno}',
file=sys.stderr)
sys.exit(1)
l = l[12:]
@@ -196,7 +196,7 @@ def make(filename, outfile):
section = STR
if l.startswith('msgstr['):
if not is_plural:
- print('plural without msgid_plural on %s:%d' % (infile, lno),
+ print(f'plural without msgid_plural on {infile}:{lno}',
file=sys.stderr)
sys.exit(1)
l = l.split(']', 1)[1]
@@ -204,7 +204,7 @@ def make(filename, outfile):
msgstr += b'\0' # Separator of the various plural forms
else:
if is_plural:
- print('indexed msgstr required for plural on %s:%d' % (infile, lno),
+ print(f'indexed msgstr required for plural on {infile}:{lno}',
file=sys.stderr)
sys.exit(1)
l = l[6:]
@@ -221,7 +221,7 @@ def make(filename, outfile):
elif section == STR:
msgstr += lb
else:
- print('Syntax error on %s:%d' % (infile, lno),
+ print(f'Syntax error on {infile}:{lno}',
'before:', file=sys.stderr)
print(l, file=sys.stderr)
sys.exit(1)
diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py
index 043d9f744a..a942973d43 100644
--- a/src/calibre/utils/date.py
+++ b/src/calibre/utils/date.py
@@ -294,22 +294,22 @@ def fd_format_hour(dt, ampm, hr):
if ampm:
h = h%12
if l == 1:
- return '%d'%h
- return '%02d'%h
+ return f'{h}'
+ return f'{h:02}'
def fd_format_minute(dt, ampm, min):
l = len(min)
if l == 1:
- return '%d'%dt.minute
- return '%02d'%dt.minute
+ return f'{dt.minute}'
+ return f'{dt.minute:02}'
def fd_format_second(dt, ampm, sec):
l = len(sec)
if l == 1:
- return '%d'%dt.second
- return '%02d'%dt.second
+ return f'{dt.second}'
+ return f'{dt.second:02}'
def fd_format_ampm(dt, ampm, ap):
@@ -322,25 +322,25 @@ def fd_format_ampm(dt, ampm, ap):
def fd_format_day(dt, ampm, dy):
l = len(dy)
if l == 1:
- return '%d'%dt.day
+ return f'{dt.day}'
if l == 2:
- return '%02d'%dt.day
+ return f'{dt.day:02}'
return lcdata['abday' if l == 3 else 'day'][(dt.weekday() + 1) % 7]
def fd_format_month(dt, ampm, mo):
l = len(mo)
if l == 1:
- return '%d'%dt.month
+ return f'{dt.month}'
if l == 2:
- return '%02d'%dt.month
+ return f'{dt.month:02}'
return lcdata['abmon' if l == 3 else 'mon'][dt.month - 1]
def fd_format_year(dt, ampm, yr):
if len(yr) == 2:
- return '%02d'%(dt.year % 100)
- return '%04d'%dt.year
+ return f'{dt.year % 100:02}'
+ return f'{dt.year:04}'
fd_function_index = {
diff --git a/src/calibre/utils/exim.py b/src/calibre/utils/exim.py
index 581e158b2a..b28cfcee9f 100644
--- a/src/calibre/utils/exim.py
+++ b/src/calibre/utils/exim.py
@@ -377,9 +377,7 @@ class Importer:
raise ValueError(f'The exported data in {name} is not valid, tail too small')
part_num, version, is_last = struct.unpack(Exporter.TAIL_FMT, raw)
if version > Exporter.VERSION:
- raise ValueError('The exported data in %s is not valid,'
- ' version (%d) is higher than maximum supported version.'
- ' You might need to upgrade calibre first.' % (name, version))
+ raise ValueError(f'The exported data in {name} is not valid, version ({version}) is higher than maximum supported version. You might need to upgrade calibre first.')
part_map[part_num] = path, is_last, size_of_part
if self.version == -1:
self.version = version
diff --git a/src/calibre/utils/fonts/sfnt/cff/table.py b/src/calibre/utils/fonts/sfnt/cff/table.py
index 17f1a9c10f..1f12178244 100644
--- a/src/calibre/utils/fonts/sfnt/cff/table.py
+++ b/src/calibre/utils/fonts/sfnt/cff/table.py
@@ -25,8 +25,7 @@ class CFF:
(self.major_version, self.minor_version, self.header_size,
self.offset_size) = unpack_from(b'>4B', raw)
if (self.major_version, self.minor_version) != (1, 0):
- raise UnsupportedFont('The CFF table has unknown version: '
- '(%d, %d)'%(self.major_version, self.minor_version))
+ raise UnsupportedFont(f'The CFF table has unknown version: ({self.major_version}, {self.minor_version})')
offset = self.header_size
# Read Names Index
@@ -105,7 +104,7 @@ class Index(list):
for i in range(offset, offset+3*(count+1), 3)]
else:
fmt = {1:'B', 2:'H', 4:'L'}[self.offset_size]
- fmt = ('>%d%s'%(count+1, fmt)).encode('ascii')
+ fmt = f'>{count + 1}{fmt}'.encode('ascii')
offsets = unpack_from(fmt, raw, offset)
offset += self.offset_size * (count+1) - 1
@@ -141,15 +140,14 @@ class Charset(list):
f = {0:self.parse_fmt0, 1:self.parse_fmt1,
2:partial(self.parse_fmt1, is_two_byte=True)}.get(fmt, None)
if f is None:
- raise UnsupportedFont('This font uses unsupported charset '
- 'table format: %d'%fmt)
+ raise UnsupportedFont(f'This font uses unsupported charset table format: {fmt}')
f(raw, offset, strings, num_glyphs, is_CID)
def parse_fmt0(self, raw, offset, strings, num_glyphs, is_CID):
- fmt = ('>%dH'%(num_glyphs-1)).encode('ascii')
+ fmt = f'>{num_glyphs - 1}H'.encode('ascii')
ids = unpack_from(fmt, raw, offset)
if is_CID:
- ids = ('cid%05d'%x for x in ids)
+ ids = (f'cid{x:05}' for x in ids)
else:
ids = (strings[x] for x in ids)
self.extend(ids)
@@ -163,7 +161,7 @@ class Charset(list):
first, nleft = unpack_from(fmt, raw, offset)
offset += sz
count += nleft + 1
- self.extend('cid%05d'%x if is_CID else strings[x] for x in
+ self.extend(f'cid{x:05}' if is_CID else strings[x] for x in
range(first, first + nleft+1))
def lookup(self, glyph_id):
diff --git a/src/calibre/utils/fonts/sfnt/common.py b/src/calibre/utils/fonts/sfnt/common.py
index 2e1421e494..4e00251e86 100644
--- a/src/calibre/utils/fonts/sfnt/common.py
+++ b/src/calibre/utils/fonts/sfnt/common.py
@@ -177,12 +177,12 @@ class Coverage:
if self.format not in {1, 2}:
raise UnsupportedFont(f'Unknown Coverage format: 0x{self.format:x} in {parent_table_name}')
if self.format == 1:
- self.glyph_ids = data.unpack('%dH'%count, single_special=False)
+ self.glyph_ids = data.unpack(f'{count}H', single_special=False)
self.glyph_ids_map = {gid:i for i, gid in
enumerate(self.glyph_ids)}
else:
self.ranges = []
- ranges = data.unpack('%dH'%(3*count), single_special=False)
+ ranges = data.unpack(f'{3 * count}H', single_special=False)
for i in range(count):
start, end, start_coverage_index = ranges[i*3:(i+1)*3]
self.ranges.append(CoverageRange(start, end, start_coverage_index))
@@ -229,13 +229,13 @@ class UnknownLookupSubTable:
def read_sets(self, data, read_item=None, set_is_index=False):
count = data.unpack('H')
- sets = data.unpack('%dH'%count, single_special=False)
+ sets = data.unpack(f'{count}H', single_special=False)
coverage_to_items_map = []
for offset in sets:
# Read items in the set
data.offset = start_pos = offset + data.start_pos
count = data.unpack('H')
- item_offsets = data.unpack('%dH'%count, single_special=False)
+ item_offsets = data.unpack(f'{count}H', single_special=False)
items = []
for offset in item_offsets:
data.offset = offset + start_pos
diff --git a/src/calibre/utils/fonts/sfnt/gsub.py b/src/calibre/utils/fonts/sfnt/gsub.py
index 1475b93f06..ef5b762534 100644
--- a/src/calibre/utils/fonts/sfnt/gsub.py
+++ b/src/calibre/utils/fonts/sfnt/gsub.py
@@ -23,7 +23,7 @@ class SingleSubstitution(UnknownLookupSubTable):
self.delta = data.unpack('h')
else:
count = data.unpack('H')
- self.substitutes = data.unpack('%dH'%count, single_special=False)
+ self.substitutes = data.unpack(f'{count}H', single_special=False)
def all_substitutions(self, glyph_ids):
gid_index_map = self.coverage.coverage_indices(glyph_ids)
@@ -61,7 +61,7 @@ class LigatureSubstitution(UnknownLookupSubTable):
def read_ligature(self, data):
lig_glyph, count = data.unpack('HH')
- components = data.unpack('%dH'%(count-1), single_special=False)
+ components = data.unpack(f'{count - 1}H', single_special=False)
return lig_glyph, components
def all_substitutions(self, glyph_ids):
@@ -113,16 +113,16 @@ class ReverseChainSingleSubstitution(UnknownLookupSubTable):
def initialize(self, data):
backtrack_count = data.unpack('H')
- backtrack_offsets = data.unpack('%dH'%backtrack_count,
+ backtrack_offsets = data.unpack(f'{backtrack_count}H',
single_special=False)
lookahead_count = data.unpack('H')
- lookahead_offsets = data.unpack('%dH'%lookahead_count,
+ lookahead_offsets = data.unpack(f'{lookahead_count}H',
single_special=False)
backtrack_offsets = [data.start_pos + x for x in backtrack_offsets]
lookahead_offsets = [data.start_pos + x for x in lookahead_offsets]
backtrack_offsets, lookahead_offsets # TODO: Use these
count = data.unpack('H')
- self.substitutes = data.unpack('%dH'%count)
+ self.substitutes = data.unpack(f'{count}H')
def all_substitutions(self, glyph_ids):
gid_index_map = self.coverage.coverage_indices(glyph_ids)
diff --git a/src/calibre/utils/fonts/sfnt/subset.py b/src/calibre/utils/fonts/sfnt/subset.py
index 0b1d64dbec..c1a825fb85 100644
--- a/src/calibre/utils/fonts/sfnt/subset.py
+++ b/src/calibre/utils/fonts/sfnt/subset.py
@@ -235,8 +235,8 @@ def print_stats(old_stats, new_stats):
suffix = ' | same size'
if nsz != osz:
suffix = f' | reduced to {nsz/osz*100:.1f} %'
- prints('%4s'%table, ' ', '%10s'%osz, ' ', f'{op:5.1f} %', ' ',
- '%10s'%nsz, ' ', f'{np:5.1f} %', suffix)
+ prints(f'{table:4}', ' ', f'{osz:10}', ' ', f'{op:5.1f} %', ' ',
+ f'{nsz:10}', ' ', f'{np:5.1f} %', suffix)
prints('='*80)
diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py
index 2b7563c968..fc15fc4032 100644
--- a/src/calibre/utils/fonts/utils.py
+++ b/src/calibre/utils/fonts/utils.py
@@ -174,7 +174,7 @@ def decode_name_record(recs):
if codec is None:
continue
try:
- windows_names[language_id] = src.decode('utf-%d-be'%codec)
+ windows_names[language_id] = src.decode(f'utf-{codec}-be')
except ValueError:
continue
diff --git a/src/calibre/utils/https.py b/src/calibre/utils/https.py
index 0b3f94a8e2..62b89ff93d 100644
--- a/src/calibre/utils/https.py
+++ b/src/calibre/utils/https.py
@@ -16,8 +16,7 @@ from polyglot.urllib import urlsplit
class HTTPError(ValueError):
def __init__(self, url, code):
- msg = '%s returned an unsupported http response code: %d (%s)' % (
- url, code, http_client.responses.get(code, None))
+ msg = f'{url} returned an unsupported http response code: {code} ({http_client.responses.get(code, None)})'
ValueError.__init__(self, msg)
self.code = code
self.url = url
diff --git a/src/calibre/utils/icu_test.py b/src/calibre/utils/icu_test.py
index f8ff9f718d..f82245242a 100644
--- a/src/calibre/utils/icu_test.py
+++ b/src/calibre/utils/icu_test.py
@@ -245,7 +245,7 @@ class TestICU(unittest.TestCase):
('a-b-c-', 'a-b-c-d a-b-c- d', 8),
):
fpos = index_of(needle, haystack)
- self.ae(pos, fpos, 'Failed to find index of %r in %r (%d != %d)' % (needle, haystack, pos, fpos))
+ self.ae(pos, fpos, f'Failed to find index of {needle!r} in {haystack!r} ({pos} != {fpos})')
def test_remove_accents(self):
for func in (icu.remove_accents_icu, icu.remove_accents_regex):
diff --git a/src/calibre/utils/ipc/server.py b/src/calibre/utils/ipc/server.py
index ca098195ac..2d4ed61fb4 100644
--- a/src/calibre/utils/ipc/server.py
+++ b/src/calibre/utils/ipc/server.py
@@ -118,7 +118,7 @@ class Server(Thread):
def launch_worker(self, gui=False, redirect_output=None, job_name=None):
start = time.monotonic()
id = next(self.launched_worker_counter)
- fd, rfile = tempfile.mkstemp(prefix='ipc_result_%d_%d_'%(self.id, id),
+ fd, rfile = tempfile.mkstemp(prefix=f'ipc_result_{self.id}_{id}_',
dir=base_dir(), suffix='.pickle')
os.close(fd)
if redirect_output is None:
diff --git a/src/calibre/utils/iso8601.py b/src/calibre/utils/iso8601.py
index 6e40b3a579..3a95946c81 100644
--- a/src/calibre/utils/iso8601.py
+++ b/src/calibre/utils/iso8601.py
@@ -21,7 +21,7 @@ def parse_iso8601(date_string, assume_utc=False, as_utc=True, require_aware=Fals
tz = utc_tz
else:
sign = '-' if tzseconds < 0 else '+'
- description = '%s%02d:%02d' % (sign, abs(tzseconds) // 3600, (abs(tzseconds) % 3600) // 60)
+ description = f'{sign}{abs(tzseconds) // 3600:02}:{abs(tzseconds) % 3600 // 60:02}'
tz = timezone(timedelta(seconds=tzseconds), description)
elif require_aware:
raise ValueError(f'{date_string} does not specify a time zone')
diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py
index d2c9b9b4c4..dd7b426677 100644
--- a/src/calibre/utils/mdns.py
+++ b/src/calibre/utils/mdns.py
@@ -141,7 +141,7 @@ def create_service(desc, service_type, port, properties, add_hostname, use_ip_ad
if add_hostname:
try:
- desc += ' (on %s port %d)'%(hostname, port)
+ desc += f' (on {hostname} port {port})'
except:
try:
desc += f' (on {hostname})'
diff --git a/src/calibre/utils/mem.py b/src/calibre/utils/mem.py
index f04e0859a2..72d0acc9ad 100644
--- a/src/calibre/utils/mem.py
+++ b/src/calibre/utils/mem.py
@@ -47,5 +47,4 @@ def diff_hists(h1, h2):
if k not in h2:
h2[k] = 0
if h1[k] != h2[k]:
- print('%s: %d -> %d (%s%d)' % (
- k, h1[k], h2[k], (h2[k] > h1[k] and '+') or '', h2[k] - h1[k]))
+ print(f"{k}: {h1[k]} -> {h2[k]} ({h2[k] > h1[k] and '+' or ''}{h2[k] - h1[k]})")
diff --git a/src/calibre/utils/terminal.py b/src/calibre/utils/terminal.py
index 1743f26e58..047b457528 100644
--- a/src/calibre/utils/terminal.py
+++ b/src/calibre/utils/terminal.py
@@ -26,7 +26,7 @@ if iswindows:
def fmt(code):
- return '\033[%dm' % code
+ return f'\x1b[{code}m'
def polyglot_write(stream, is_binary, encoding, text):
diff --git a/src/calibre/utils/wmf/__init__.py b/src/calibre/utils/wmf/__init__.py
index 4e85b1cb73..c2173cd343 100644
--- a/src/calibre/utils/wmf/__init__.py
+++ b/src/calibre/utils/wmf/__init__.py
@@ -38,7 +38,7 @@ class DIBHeader:
'bits_per_pixel')):
setattr(self, attr, parts[i])
else:
- raise ValueError('Unsupported DIB header type of size: %d'%hsize)
+ raise ValueError(f'Unsupported DIB header type of size: {hsize}')
self.bitmasks_size = 12 if getattr(self, 'compression', 0) == 3 else 0
self.color_table_size = 0
diff --git a/src/calibre/utils/zipfile.py b/src/calibre/utils/zipfile.py
index 21a6f25534..dccb818f44 100644
--- a/src/calibre/utils/zipfile.py
+++ b/src/calibre/utils/zipfile.py
@@ -1457,8 +1457,7 @@ class ZipFile:
# check for valid comment length
if len(self.comment) >= ZIP_MAX_COMMENT:
if self.debug > 0:
- msg = 'Archive comment is too long; truncating to %d bytes' \
- % ZIP_MAX_COMMENT
+ msg = f'Archive comment is too long; truncating to {ZIP_MAX_COMMENT} bytes'
print(msg)
self.comment = self.comment[:ZIP_MAX_COMMENT]
diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py
index 89f9863357..aa35a9ac35 100644
--- a/src/calibre/web/feeds/__init__.py
+++ b/src/calibre/web/feeds/__init__.py
@@ -333,7 +333,7 @@ class FeedCollection(list):
for article, feed in self.duplicates:
art = copy.deepcopy(article)
j, i = self.find_article(article)
- art.url = '../feed_%d/article_%d/index.html'%(j, i)
+ art.url = f'../feed_{j}/article_{i}/index.html'
temp.append((feed, art))
for feed, art in temp:
feed.articles.append(art)
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index f5b5ada004..2463e19c65 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -1196,7 +1196,7 @@ class BasicNewsRecipe(Recipe):
if bn:
bn = bn.rpartition('/')[-1]
if bn:
- img = os.path.join(imgdir, 'feed_image_%d%s'%(self.image_counter, os.path.splitext(bn)[-1]))
+ img = os.path.join(imgdir, f'feed_image_{self.image_counter}{os.path.splitext(bn)[-1]}')
try:
with open(img, 'wb') as fi, closing(self.browser.open(feed.image_url, timeout=self.timeout)) as r:
fi.write(r.read())
@@ -1336,14 +1336,14 @@ class BasicNewsRecipe(Recipe):
self.feed_objects = feeds
for f, feed in enumerate(feeds):
- feed_dir = os.path.join(self.output_dir, 'feed_%d'%f)
+ feed_dir = os.path.join(self.output_dir, f'feed_{f}')
if not os.path.isdir(feed_dir):
os.makedirs(feed_dir)
for a, article in enumerate(feed):
if a >= self.max_articles_per_feed:
break
- art_dir = os.path.join(feed_dir, 'article_%d'%a)
+ art_dir = os.path.join(feed_dir, f'article_{a}')
if not os.path.isdir(art_dir):
os.makedirs(art_dir)
try:
@@ -1385,7 +1385,7 @@ class BasicNewsRecipe(Recipe):
for f, feed in enumerate(feeds):
html = self.feed2index(f, feeds)
- feed_dir = os.path.join(self.output_dir, 'feed_%d'%f)
+ feed_dir = os.path.join(self.output_dir, f'feed_{f}')
with open(os.path.join(feed_dir, 'index.html'), 'wb') as fi:
fi.write(html)
self.create_opf(feeds)
@@ -1583,7 +1583,7 @@ class BasicNewsRecipe(Recipe):
ref.title = 'Masthead Image'
opf.guide.append(ref)
- manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
+ manifest = [os.path.join(dir, f'feed_{i}') for i in range(len(feeds))]
manifest.append(os.path.join(dir, 'index.html'))
manifest.append(os.path.join(dir, 'index.ncx'))
@@ -1620,7 +1620,7 @@ class BasicNewsRecipe(Recipe):
f = feeds[num]
for j, a in enumerate(f):
if getattr(a, 'downloaded', False):
- adir = 'feed_%d/article_%d/'%(num, j)
+ adir = f'feed_{num}/article_{j}/'
auth = a.author
if not auth:
auth = None
@@ -1678,7 +1678,7 @@ class BasicNewsRecipe(Recipe):
if len(feeds) > 1:
for i, f in enumerate(feeds):
- entries.append('feed_%d/index.html'%i)
+ entries.append(f'feed_{i}/index.html')
po = self.play_order_map.get(entries[-1], None)
if po is None:
self.play_order_counter += 1
@@ -1689,7 +1689,7 @@ class BasicNewsRecipe(Recipe):
desc = getattr(f, 'description', None)
if not desc:
desc = None
- feed_index(i, toc.add_item('feed_%d/index.html'%i, None,
+ feed_index(i, toc.add_item(f'feed_{i}/index.html', None,
f.title, play_order=po, description=desc, author=auth))
else:
@@ -1715,7 +1715,7 @@ class BasicNewsRecipe(Recipe):
article = request.article
self.log.debug('Downloaded article:', article.title, 'from', article.url)
article.orig_url = article.url
- article.url = 'article_%d/index.html'%a
+ article.url = f'article_{a}/index.html'
article.downloaded = True
article.sub_pages = result[1][1:]
self.jobs_done += 1
diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py
index b47eea443a..18b02f9d9d 100644
--- a/src/calibre/web/feeds/recipes/collection.py
+++ b/src/calibre/web/feeds/recipes/collection.py
@@ -430,7 +430,7 @@ class SchedulerConfig:
text = '%d:%d:%d'%schedule
elif typ in ('days_of_week', 'days_of_month'):
dw = ','.join(map(str, map(int, schedule[0])))
- text = '%s:%d:%d'%(dw, schedule[1], schedule[2])
+ text = f'{dw}:{schedule[1]}:{schedule[2]}'
else:
raise ValueError(f'Unknown schedule type: {typ!r}')
s.text = text
diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py
index 7e484b5f88..cd354cc000 100644
--- a/src/calibre/web/feeds/templates.py
+++ b/src/calibre/web/feeds/templates.py
@@ -103,7 +103,7 @@ class IndexTemplate(Template):
for i, feed in enumerate(feeds):
if len(feed):
li = LI(A(feed.title, attrs('feed', rescale=120,
- href='feed_%d/index.html'%i)), id='feed_%d'%i)
+ href=f'feed_{i}/index.html')), id=f'feed_{i}')
ul.append(li)
div = DIV(
PT(IMG(src=masthead, alt='masthead'), style='text-align:center'),
@@ -129,14 +129,14 @@ class FeedTemplate(Template):
hr.tail = '| '
if f+1 < len(feeds):
- link = A(_('Next section'), href='../feed_%d/index.html'%(f+1))
+ link = A(_('Next section'), href=f'../feed_{f + 1}/index.html')
link.tail = ' | '
navbar.append(link)
link = A(_('Main menu'), href='../index.html')
link.tail = ' | '
navbar.append(link)
if f > 0:
- link = A(_('Previous section'), href='../feed_%d/index.html'%(f-1))
+ link = A(_('Previous section'), href=f'../feed_{f - 1}/index.html')
link.tail = ' |'
navbar.append(link)
if top:
@@ -178,7 +178,7 @@ class FeedTemplate(Template):
A(article.title, attrs('article', rescale=120,
href=article.url)),
SPAN(article.formatted_date, attrs('article_date')),
- attrs(rescale=100, id='article_%d'%i,
+ attrs(rescale=100, id=f'article_{i}',
style='padding-bottom:0.5em')
)
if article.summary:
@@ -220,20 +220,20 @@ class NavBarTemplate(Template):
navbar.append(BR())
navbar.append(BR())
else:
- next_art = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \
- else 'article_%d'%(art+1)
+ next_art = f'feed_{feed + 1}' if art == number_of_articles_in_feed - 1 \
+ else f'article_{art + 1}'
up = '../..' if art == number_of_articles_in_feed - 1 else '..'
href = f'{prefix}{up}/{next_art}/index.html'
navbar.text = '| '
navbar.append(A(_('Next'), href=href))
- href = '%s../index.html#article_%d'%(prefix, art)
+ href = f'{prefix}../index.html#article_{art}'
next(navbar.iterchildren(reversed=True)).tail = ' | '
navbar.append(A(_('Section menu'), href=href))
- href = '%s../../index.html#feed_%d'%(prefix, feed)
+ href = f'{prefix}../../index.html#feed_{feed}'
next(navbar.iterchildren(reversed=True)).tail = ' | '
navbar.append(A(_('Main menu'), href=href))
if art > 0 and not bottom:
- href = '%s../article_%d/index.html'%(prefix, art-1)
+ href = f'{prefix}../article_{art - 1}/index.html'
next(navbar.iterchildren(reversed=True)).tail = ' | '
navbar.append(A(_('Previous'), href=href))
next(navbar.iterchildren(reversed=True)).tail = ' | '
@@ -266,7 +266,7 @@ class TouchscreenIndexTemplate(Template):
for i, feed in enumerate(feeds):
if len(feed):
tr = TR()
- tr.append(TD(attrs(rescale=120), A(feed.title, href='feed_%d/index.html'%i)))
+ tr.append(TD(attrs(rescale=120), A(feed.title, href=f'feed_{i}/index.html')))
tr.append(TD(f'{len(feed.articles)}', style='text-align:right'))
toc.append(tr)
div = DIV(
@@ -402,21 +402,21 @@ class TouchscreenNavBarTemplate(Template):
navbar.append(BR())
# | Previous
if art > 0:
- link = A(attrs('article_link'),_('Previous'),href='%s../article_%d/index.html'%(prefix, art-1))
+ link = A(attrs('article_link'),_('Previous'),href=f'{prefix}../article_{art - 1}/index.html')
navbar_tr.append(TD(attrs('article_prev'),link))
else:
navbar_tr.append(TD(attrs('article_prev'),''))
# | Articles | Sections |
- link = A(attrs('articles_link'),_('Articles'), href='%s../index.html#article_%d'%(prefix, art))
+ link = A(attrs('articles_link'),_('Articles'), href=f'{prefix}../index.html#article_{art}')
navbar_tr.append(TD(attrs('article_articles_list'),link))
- link = A(attrs('sections_link'),_('Sections'), href='%s../../index.html#feed_%d'%(prefix, feed))
+ link = A(attrs('sections_link'),_('Sections'), href=f'{prefix}../../index.html#feed_{feed}')
navbar_tr.append(TD(attrs('article_sections_list'),link))
# | Next
- next_art = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \
- else 'article_%d'%(art+1)
+ next_art = f'feed_{feed + 1}' if art == number_of_articles_in_feed - 1 \
+ else f'article_{art + 1}'
up = '../..' if art == number_of_articles_in_feed - 1 else '..'
link = A(attrs('article_link'), _('Next'), href=f'{prefix}{up}/{next_art}/index.html')
diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py
index 02b434248d..45dc1ecece 100644
--- a/src/calibre/web/fetch/simple.py
+++ b/src/calibre/web/fetch/simple.py
@@ -79,7 +79,7 @@ def basename(url):
except:
global bad_url_counter
bad_url_counter += 1
- return 'bad_url_%d.html'%bad_url_counter
+ return f'bad_url_{bad_url_counter}.html'
if not os.path.splitext(res)[1]:
return 'index.html'
return res