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