From 5c7dc9613b683b9dea37cb1f6b482405fe3d22c8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Jan 2025 10:51:35 +0530 Subject: [PATCH] Automated conversion of % format specifiers Using ruff. Does not change any translatable strings. There are still several thousand usages of % left that ruff wont auto-convert. Get to them someday. --- icons/icns/make_iconsets.py | 2 +- setup/build.py | 4 +- setup/check.py | 2 +- setup/hosting.py | 8 +- setup/install.py | 2 +- setup/plugins_mirror.py | 2 +- setup/publish.py | 12 +- setup/upload.py | 5 +- setup/win-ci.py | 2 +- src/calibre/__init__.py | 2 +- src/calibre/constants.py | 4 +- src/calibre/customize/__init__.py | 10 +- src/calibre/customize/conversion.py | 7 +- src/calibre/customize/ui.py | 10 +- src/calibre/customize/zipplugin.py | 19 +- src/calibre/db/backend.py | 70 +++-- src/calibre/db/cache.py | 10 +- src/calibre/db/categories.py | 3 +- src/calibre/db/cli/cmd_add.py | 2 +- src/calibre/db/cli/cmd_add_custom_column.py | 2 +- src/calibre/db/cli/cmd_list_categories.py | 2 +- .../db/cli/cmd_remove_custom_column.py | 2 +- src/calibre/db/fields.py | 4 +- src/calibre/db/lazy.py | 4 +- src/calibre/db/legacy.py | 22 +- src/calibre/db/restore.py | 8 +- src/calibre/db/schema_upgrades.py | 115 +++++---- src/calibre/db/search.py | 2 +- src/calibre/db/tables.py | 2 +- src/calibre/db/tests/add_remove.py | 4 +- src/calibre/db/tests/base.py | 6 +- src/calibre/db/tests/legacy.py | 3 +- src/calibre/db/tests/reading.py | 16 +- src/calibre/db/tests/writing.py | 11 +- src/calibre/db/utils.py | 2 +- src/calibre/db/view.py | 6 +- src/calibre/db/write.py | 22 +- src/calibre/debug.py | 2 +- src/calibre/devices/__init__.py | 2 +- src/calibre/devices/cli.py | 2 +- src/calibre/devices/cybook/driver.py | 4 +- src/calibre/devices/iriver/driver.py | 4 +- src/calibre/devices/kindle/apnx.py | 6 +- src/calibre/devices/kindle/bookmark.py | 12 +- src/calibre/devices/kindle/driver.py | 8 +- src/calibre/devices/kobo/books.py | 4 +- src/calibre/devices/kobo/driver.py | 242 +++++++++--------- src/calibre/devices/misc.py | 2 +- src/calibre/devices/mtp/driver.py | 9 +- src/calibre/devices/mtp/filesystem_cache.py | 24 +- src/calibre/devices/mtp/test.py | 2 +- src/calibre/devices/mtp/unix/driver.py | 58 ++--- src/calibre/devices/mtp/unix/sysfs.py | 6 +- src/calibre/devices/mtp/windows/driver.py | 31 +-- src/calibre/devices/nook/driver.py | 2 +- src/calibre/devices/paladin/driver.py | 12 +- src/calibre/devices/prs505/driver.py | 6 +- src/calibre/devices/prs505/sony_cache.py | 26 +- src/calibre/devices/prst1/driver.py | 12 +- src/calibre/devices/scanner.py | 8 +- .../devices/smart_device_app/driver.py | 7 +- src/calibre/devices/usbms/device.py | 4 +- src/calibre/devices/usbms/deviceconfig.py | 2 +- src/calibre/devices/usbms/driver.py | 2 +- src/calibre/devices/utils.py | 2 +- src/calibre/devices/winusb.py | 21 +- src/calibre/ebooks/__init__.py | 6 +- src/calibre/ebooks/chm/reader.py | 6 +- src/calibre/ebooks/compression/tcr.py | 2 +- src/calibre/ebooks/conversion/cli.py | 3 +- .../ebooks/conversion/plugins/chm_input.py | 6 +- .../ebooks/conversion/plugins/comic_input.py | 25 +- .../ebooks/conversion/plugins/epub_input.py | 4 +- .../ebooks/conversion/plugins/epub_output.py | 10 +- .../ebooks/conversion/plugins/fb2_input.py | 9 +- .../ebooks/conversion/plugins/html_input.py | 6 +- .../ebooks/conversion/plugins/lit_input.py | 2 +- .../ebooks/conversion/plugins/lrf_input.py | 9 +- .../ebooks/conversion/plugins/lrf_output.py | 2 +- .../ebooks/conversion/plugins/mobi_input.py | 2 +- .../ebooks/conversion/plugins/oeb_output.py | 2 +- .../ebooks/conversion/plugins/pdb_input.py | 3 +- .../ebooks/conversion/plugins/pdb_output.py | 2 +- .../ebooks/conversion/plugins/pml_input.py | 4 +- .../ebooks/conversion/plugins/recipe_input.py | 6 +- .../ebooks/conversion/plugins/rtf_input.py | 16 +- .../ebooks/conversion/plugins/snb_input.py | 4 +- .../ebooks/conversion/plugins/snb_output.py | 14 +- .../ebooks/conversion/plugins/txt_input.py | 10 +- src/calibre/ebooks/conversion/plumber.py | 10 +- src/calibre/ebooks/conversion/preprocess.py | 38 ++- src/calibre/ebooks/conversion/utils.py | 18 +- src/calibre/ebooks/covers.py | 2 +- src/calibre/ebooks/djvu/djvubzzdec.py | 2 +- src/calibre/ebooks/docx/block_styles.py | 40 +-- src/calibre/ebooks/docx/char_styles.py | 14 +- src/calibre/ebooks/docx/cleanup.py | 6 +- src/calibre/ebooks/docx/container.py | 6 +- src/calibre/ebooks/docx/fields.py | 8 +- src/calibre/ebooks/docx/fonts.py | 6 +- src/calibre/ebooks/docx/images.py | 20 +- src/calibre/ebooks/docx/index.py | 2 +- src/calibre/ebooks/docx/names.py | 6 +- src/calibre/ebooks/docx/numbering.py | 7 +- src/calibre/ebooks/docx/styles.py | 2 +- src/calibre/ebooks/docx/tables.py | 38 +-- src/calibre/ebooks/docx/to_html.py | 29 +-- src/calibre/ebooks/docx/writer/container.py | 6 +- src/calibre/ebooks/docx/writer/fonts.py | 2 +- src/calibre/ebooks/docx/writer/from_html.py | 6 +- src/calibre/ebooks/docx/writer/images.py | 4 +- src/calibre/ebooks/docx/writer/links.py | 6 +- src/calibre/ebooks/docx/writer/styles.py | 44 ++-- src/calibre/ebooks/docx/writer/tables.py | 8 +- src/calibre/ebooks/epub/__init__.py | 9 +- src/calibre/ebooks/epub/cfi/parse.py | 26 +- src/calibre/ebooks/fb2/fb2ml.py | 46 ++-- src/calibre/ebooks/html/input.py | 6 +- src/calibre/ebooks/htmlz/oeb2html.py | 27 +- src/calibre/ebooks/lit/mssha1.py | 4 +- src/calibre/ebooks/lit/reader.py | 18 +- src/calibre/ebooks/lit/writer.py | 10 +- src/calibre/ebooks/lrf/__init__.py | 6 +- src/calibre/ebooks/lrf/fonts.py | 2 +- src/calibre/ebooks/lrf/html/convert_from.py | 31 ++- src/calibre/ebooks/lrf/input.py | 20 +- src/calibre/ebooks/lrf/lrfparser.py | 26 +- src/calibre/ebooks/lrf/objects.py | 75 +++--- src/calibre/ebooks/lrf/pylrs/elements.py | 6 +- src/calibre/ebooks/lrf/pylrs/pylrf.py | 6 +- src/calibre/ebooks/lrf/pylrs/pylrs.py | 25 +- src/calibre/ebooks/lrf/tags.py | 16 +- src/calibre/ebooks/metadata/__init__.py | 6 +- src/calibre/ebooks/metadata/book/base.py | 20 +- src/calibre/ebooks/metadata/book/render.py | 21 +- src/calibre/ebooks/metadata/docx.py | 2 +- src/calibre/ebooks/metadata/fb2.py | 14 +- src/calibre/ebooks/metadata/html.py | 2 +- src/calibre/ebooks/metadata/imp.py | 2 +- src/calibre/ebooks/metadata/kfx.py | 5 +- src/calibre/ebooks/metadata/mobi.py | 8 +- src/calibre/ebooks/metadata/odt.py | 14 +- src/calibre/ebooks/metadata/opf2.py | 60 +++-- src/calibre/ebooks/metadata/opf3.py | 26 +- src/calibre/ebooks/metadata/opf3_test.py | 12 +- src/calibre/ebooks/metadata/pdf.py | 2 +- src/calibre/ebooks/metadata/rb.py | 2 +- src/calibre/ebooks/metadata/rtf.py | 10 +- src/calibre/ebooks/metadata/toc.py | 5 +- src/calibre/ebooks/metadata/topaz.py | 12 +- src/calibre/ebooks/metadata/worker.py | 2 +- src/calibre/ebooks/metadata/xmp.py | 8 +- src/calibre/ebooks/metadata/zip.py | 2 +- src/calibre/ebooks/mobi/debug/containers.py | 12 +- src/calibre/ebooks/mobi/debug/headers.py | 95 ++++--- src/calibre/ebooks/mobi/debug/index.py | 7 +- src/calibre/ebooks/mobi/debug/mobi6.py | 46 ++-- src/calibre/ebooks/mobi/debug/mobi8.py | 8 +- src/calibre/ebooks/mobi/reader/headers.py | 2 +- src/calibre/ebooks/mobi/reader/index.py | 8 +- src/calibre/ebooks/mobi/reader/markup.py | 28 +- src/calibre/ebooks/mobi/reader/mobi6.py | 21 +- src/calibre/ebooks/mobi/reader/mobi8.py | 17 +- src/calibre/ebooks/mobi/tweak.py | 2 +- src/calibre/ebooks/mobi/utils.py | 4 +- src/calibre/ebooks/mobi/writer2/indexer.py | 8 +- src/calibre/ebooks/mobi/writer2/resources.py | 6 +- src/calibre/ebooks/mobi/writer2/serializer.py | 4 +- src/calibre/ebooks/mobi/writer8/exth.py | 2 +- src/calibre/ebooks/mobi/writer8/header.py | 6 +- src/calibre/ebooks/mobi/writer8/index.py | 13 +- src/calibre/ebooks/mobi/writer8/main.py | 18 +- src/calibre/ebooks/mobi/writer8/skeleton.py | 18 +- src/calibre/ebooks/mobi/writer8/toc.py | 2 +- src/calibre/ebooks/odt/input.py | 6 +- src/calibre/ebooks/oeb/base.py | 24 +- src/calibre/ebooks/oeb/display/webview.py | 4 +- src/calibre/ebooks/oeb/normalize_css.py | 24 +- src/calibre/ebooks/oeb/parse_utils.py | 21 +- src/calibre/ebooks/oeb/polish/cascade.py | 2 +- src/calibre/ebooks/oeb/polish/check/base.py | 2 +- src/calibre/ebooks/oeb/polish/check/links.py | 2 +- src/calibre/ebooks/oeb/polish/check/opf.py | 6 +- .../ebooks/oeb/polish/check/parsing.py | 2 +- src/calibre/ebooks/oeb/polish/container.py | 18 +- src/calibre/ebooks/oeb/polish/cover.py | 8 +- src/calibre/ebooks/oeb/polish/create.py | 18 +- src/calibre/ebooks/oeb/polish/embed.py | 22 +- src/calibre/ebooks/oeb/polish/import_book.py | 2 +- src/calibre/ebooks/oeb/polish/jacket.py | 2 +- src/calibre/ebooks/oeb/polish/main.py | 2 +- src/calibre/ebooks/oeb/polish/opf.py | 4 +- src/calibre/ebooks/oeb/polish/parsing.py | 4 +- src/calibre/ebooks/oeb/polish/pretty.py | 2 +- src/calibre/ebooks/oeb/polish/replace.py | 8 +- src/calibre/ebooks/oeb/polish/report.py | 2 +- src/calibre/ebooks/oeb/polish/spell.py | 6 +- src/calibre/ebooks/oeb/polish/split.py | 4 +- src/calibre/ebooks/oeb/polish/stats.py | 2 +- src/calibre/ebooks/oeb/polish/subset.py | 6 +- .../ebooks/oeb/polish/tests/cascade.py | 2 +- .../ebooks/oeb/polish/tests/container.py | 4 +- .../ebooks/oeb/polish/tests/parsing.py | 24 +- .../ebooks/oeb/polish/tests/structure.py | 26 +- src/calibre/ebooks/oeb/polish/toc.py | 18 +- src/calibre/ebooks/oeb/polish/utils.py | 2 +- src/calibre/ebooks/oeb/reader.py | 42 ++- src/calibre/ebooks/oeb/stylizer.py | 20 +- src/calibre/ebooks/oeb/transforms/cover.py | 2 +- .../ebooks/oeb/transforms/embed_fonts.py | 4 +- src/calibre/ebooks/oeb/transforms/flatcss.py | 49 ++-- src/calibre/ebooks/oeb/transforms/guide.py | 2 +- src/calibre/ebooks/oeb/transforms/htmltoc.py | 2 +- src/calibre/ebooks/oeb/transforms/jacket.py | 12 +- src/calibre/ebooks/oeb/transforms/metadata.py | 6 +- .../ebooks/oeb/transforms/page_margin.py | 6 +- .../ebooks/oeb/transforms/rasterize.py | 4 +- src/calibre/ebooks/oeb/transforms/rescale.py | 10 +- src/calibre/ebooks/oeb/transforms/split.py | 13 +- .../ebooks/oeb/transforms/structure.py | 16 +- src/calibre/ebooks/oeb/transforms/subset.py | 7 +- .../ebooks/oeb/transforms/trimmanifest.py | 2 +- src/calibre/ebooks/oeb/writer.py | 2 +- src/calibre/ebooks/pdb/ereader/inspector.py | 8 +- src/calibre/ebooks/pdb/ereader/reader.py | 2 +- src/calibre/ebooks/pdb/ereader/reader132.py | 8 +- src/calibre/ebooks/pdb/ereader/reader202.py | 5 +- src/calibre/ebooks/pdb/ereader/writer.py | 4 +- src/calibre/ebooks/pdb/haodoo/reader.py | 2 +- src/calibre/ebooks/pdb/plucker/reader.py | 24 +- src/calibre/ebooks/pdf/html_writer.py | 4 +- src/calibre/ebooks/pdf/reflow.py | 16 +- src/calibre/ebooks/pdf/render/common.py | 6 +- src/calibre/ebooks/pdf/render/fonts.py | 12 +- src/calibre/ebooks/pdf/render/graphics.py | 2 +- src/calibre/ebooks/pdf/render/links.py | 9 +- src/calibre/ebooks/pdf/render/serialize.py | 20 +- src/calibre/ebooks/pml/pmlconverter.py | 33 +-- src/calibre/ebooks/pml/pmlml.py | 32 +-- src/calibre/ebooks/rb/rbml.py | 28 +- src/calibre/ebooks/rb/reader.py | 6 +- src/calibre/ebooks/rb/writer.py | 10 +- src/calibre/ebooks/readability/cleaners.py | 2 +- src/calibre/ebooks/readability/debug.py | 2 +- src/calibre/ebooks/readability/readability.py | 19 +- src/calibre/ebooks/rtf/rtfml.py | 19 +- src/calibre/ebooks/rtf2xml/ParseRtf.py | 10 +- src/calibre/ebooks/rtf2xml/add_brackets.py | 4 +- src/calibre/ebooks/rtf2xml/border_parse.py | 6 +- src/calibre/ebooks/rtf2xml/check_brackets.py | 4 +- src/calibre/ebooks/rtf2xml/check_encoding.py | 2 +- src/calibre/ebooks/rtf2xml/colors.py | 8 +- src/calibre/ebooks/rtf2xml/combine_borders.py | 3 +- src/calibre/ebooks/rtf2xml/configure_txt.py | 22 +- src/calibre/ebooks/rtf2xml/convert_to_tags.py | 14 +- src/calibre/ebooks/rtf2xml/copy.py | 2 +- src/calibre/ebooks/rtf2xml/delete_info.py | 7 +- src/calibre/ebooks/rtf2xml/field_strings.py | 80 +++--- src/calibre/ebooks/rtf2xml/fields_large.py | 13 +- src/calibre/ebooks/rtf2xml/fields_small.py | 38 +-- src/calibre/ebooks/rtf2xml/fonts.py | 6 +- src/calibre/ebooks/rtf2xml/footnote.py | 8 +- src/calibre/ebooks/rtf2xml/get_char_map.py | 6 +- src/calibre/ebooks/rtf2xml/get_options.py | 2 +- src/calibre/ebooks/rtf2xml/group_borders.py | 6 +- src/calibre/ebooks/rtf2xml/group_styles.py | 6 +- src/calibre/ebooks/rtf2xml/header.py | 4 +- .../ebooks/rtf2xml/headings_to_sections.py | 7 +- src/calibre/ebooks/rtf2xml/hex_2_utf8.py | 25 +- src/calibre/ebooks/rtf2xml/info.py | 12 +- src/calibre/ebooks/rtf2xml/inline.py | 10 +- src/calibre/ebooks/rtf2xml/list_numbers.py | 2 +- src/calibre/ebooks/rtf2xml/list_table.py | 12 +- src/calibre/ebooks/rtf2xml/make_lists.py | 16 +- src/calibre/ebooks/rtf2xml/old_rtf.py | 3 +- src/calibre/ebooks/rtf2xml/options_trem.py | 8 +- src/calibre/ebooks/rtf2xml/output.py | 4 +- src/calibre/ebooks/rtf2xml/override_table.py | 2 +- src/calibre/ebooks/rtf2xml/paragraph_def.py | 30 +-- src/calibre/ebooks/rtf2xml/preamble_div.py | 2 +- src/calibre/ebooks/rtf2xml/preamble_rest.py | 5 +- src/calibre/ebooks/rtf2xml/process_tokens.py | 28 +- src/calibre/ebooks/rtf2xml/sections.py | 18 +- src/calibre/ebooks/rtf2xml/styles.py | 38 +-- src/calibre/ebooks/rtf2xml/table.py | 4 +- src/calibre/ebooks/snb/snbml.py | 6 +- src/calibre/ebooks/textile/functions.py | 97 ++++--- src/calibre/ebooks/tweak.py | 10 +- src/calibre/ebooks/txt/markdownml.py | 4 +- src/calibre/ebooks/txt/processor.py | 6 +- src/calibre/ebooks/txt/textileml.py | 8 +- src/calibre/ebooks/txt/txtml.py | 8 +- src/calibre/gui2/actions/__init__.py | 4 +- src/calibre/gui2/actions/catalog.py | 3 +- src/calibre/gui2/actions/choose_library.py | 7 +- src/calibre/gui2/actions/copy_to_library.py | 2 +- src/calibre/gui2/actions/device.py | 7 +- src/calibre/gui2/actions/polish.py | 6 +- src/calibre/gui2/actions/toc_edit.py | 2 +- src/calibre/gui2/add.py | 2 +- src/calibre/gui2/author_mapper.py | 2 +- src/calibre/gui2/book_details.py | 15 +- src/calibre/gui2/catalog/catalog_csv_xml.py | 4 +- src/calibre/gui2/catalog/catalog_epub_mobi.py | 18 +- src/calibre/gui2/comments_editor.py | 6 +- src/calibre/gui2/convert/__init__.py | 12 +- src/calibre/gui2/convert/font_key.py | 4 +- src/calibre/gui2/convert/look_and_feel.py | 8 +- src/calibre/gui2/convert/regex_builder.py | 3 +- src/calibre/gui2/convert/xpath_wizard.py | 6 +- src/calibre/gui2/cover_flow.py | 2 +- src/calibre/gui2/covers.py | 14 +- src/calibre/gui2/css_transform_rules.py | 4 +- src/calibre/gui2/device.py | 19 +- src/calibre/gui2/device_drivers/mtp_config.py | 2 +- .../gui2/device_drivers/mtp_folder_browser.py | 2 +- src/calibre/gui2/dialogs/catalog.py | 12 +- src/calibre/gui2/dialogs/confirm_merge.py | 2 +- src/calibre/gui2/dialogs/conversion_error.py | 2 +- src/calibre/gui2/dialogs/custom_recipes.py | 4 +- src/calibre/gui2/dialogs/drm_error.py | 2 +- .../gui2/dialogs/edit_authors_dialog.py | 3 +- src/calibre/gui2/dialogs/message_box.py | 9 +- src/calibre/gui2/dialogs/metadata_bulk.py | 6 +- src/calibre/gui2/dialogs/plugin_updater.py | 6 +- src/calibre/gui2/dialogs/scheduler.py | 10 +- src/calibre/gui2/dialogs/search.py | 8 +- src/calibre/gui2/dialogs/template_dialog.py | 25 +- src/calibre/gui2/email.py | 7 +- src/calibre/gui2/html_transform_rules.py | 2 +- src/calibre/gui2/jobs.py | 4 +- src/calibre/gui2/keyboard.py | 13 +- src/calibre/gui2/layout.py | 2 +- src/calibre/gui2/library/alternate_views.py | 2 +- src/calibre/gui2/library/annotations.py | 2 +- src/calibre/gui2/lrf_renderer/main.py | 2 +- src/calibre/gui2/lrf_renderer/text.py | 4 +- src/calibre/gui2/main.py | 6 +- src/calibre/gui2/main_window.py | 2 +- src/calibre/gui2/metadata/basic_widgets.py | 4 +- src/calibre/gui2/metadata/bulk_download.py | 2 +- src/calibre/gui2/metadata/diff.py | 4 +- src/calibre/gui2/metadata/single.py | 2 +- src/calibre/gui2/metadata/single_download.py | 38 +-- src/calibre/gui2/open_with.py | 8 +- src/calibre/gui2/preferences/__init__.py | 7 +- src/calibre/gui2/preferences/coloring.py | 4 +- .../gui2/preferences/create_custom_column.py | 2 +- .../gui2/preferences/ignored_devices.py | 2 +- src/calibre/gui2/preferences/misc.py | 2 +- src/calibre/gui2/preferences/plugboard.py | 2 +- src/calibre/gui2/preferences/plugins.py | 4 +- src/calibre/gui2/preferences/save_template.py | 7 +- .../gui2/preferences/texture_chooser.py | 2 +- src/calibre/gui2/preferences/tweaks.py | 20 +- src/calibre/gui2/proceed.py | 5 +- src/calibre/gui2/publisher_mapper.py | 2 +- src/calibre/gui2/qt_file_dialogs.py | 2 +- src/calibre/gui2/search_restriction_mixin.py | 12 +- src/calibre/gui2/series_mapper.py | 2 +- .../config/chooser/adv_search_builder.py | 4 +- .../gui2/store/config/chooser/models.py | 2 +- src/calibre/gui2/store/loader.py | 2 +- .../gui2/store/search/adv_search_builder.py | 4 +- src/calibre/gui2/store/search/models.py | 12 +- src/calibre/gui2/store/search/search.py | 14 +- src/calibre/gui2/store/search_result.py | 2 +- src/calibre/gui2/tag_browser/model.py | 19 +- src/calibre/gui2/tag_browser/ui.py | 2 +- src/calibre/gui2/tag_browser/view.py | 6 +- src/calibre/gui2/tag_mapper.py | 4 +- src/calibre/gui2/threaded_jobs.py | 2 +- src/calibre/gui2/toc/location.py | 8 +- src/calibre/gui2/toc/main.py | 4 +- src/calibre/gui2/tools.py | 12 +- src/calibre/gui2/tweak_book/__init__.py | 2 +- src/calibre/gui2/tweak_book/boss.py | 6 +- src/calibre/gui2/tweak_book/check.py | 7 +- src/calibre/gui2/tweak_book/check_links.py | 2 +- .../gui2/tweak_book/completion/popup.py | 2 +- src/calibre/gui2/tweak_book/diff/main.py | 2 +- src/calibre/gui2/tweak_book/diff/view.py | 12 +- src/calibre/gui2/tweak_book/editor/canvas.py | 2 +- src/calibre/gui2/tweak_book/editor/help.py | 10 +- src/calibre/gui2/tweak_book/editor/image.py | 4 +- .../gui2/tweak_book/editor/smarts/html.py | 15 +- .../gui2/tweak_book/editor/snippets.py | 3 +- .../gui2/tweak_book/editor/syntax/html.py | 6 +- .../editor/syntax/pygments_highlighter.py | 4 +- src/calibre/gui2/tweak_book/editor/text.py | 6 +- src/calibre/gui2/tweak_book/editor/themes.py | 2 +- src/calibre/gui2/tweak_book/editor/widget.py | 12 +- src/calibre/gui2/tweak_book/file_list.py | 6 +- .../gui2/tweak_book/function_replace.py | 2 +- src/calibre/gui2/tweak_book/live_css.py | 5 +- src/calibre/gui2/tweak_book/polish.py | 2 +- src/calibre/gui2/tweak_book/preferences.py | 12 +- src/calibre/gui2/tweak_book/preview.py | 5 +- src/calibre/gui2/tweak_book/reports.py | 8 +- src/calibre/gui2/tweak_book/save.py | 5 +- src/calibre/gui2/tweak_book/search.py | 4 +- src/calibre/gui2/tweak_book/ui.py | 6 +- src/calibre/gui2/tweak_book/widgets.py | 19 +- src/calibre/gui2/ui.py | 16 +- src/calibre/gui2/update.py | 4 +- src/calibre/gui2/viewer/convert_book.py | 6 +- src/calibre/gui2/viewer/printing.py | 8 +- src/calibre/gui2/webengine.py | 3 +- src/calibre/gui2/widgets.py | 6 +- src/calibre/gui2/widgets2.py | 4 +- src/calibre/gui2/win_file_dialogs.py | 10 +- src/calibre/library/catalogs/bibtex.py | 43 ++-- src/calibre/library/catalogs/csv_xml.py | 18 +- src/calibre/library/catalogs/epub_mobi.py | 39 ++- .../library/catalogs/epub_mobi_builder.py | 204 +++++++-------- src/calibre/library/catalogs/utils.py | 37 ++- src/calibre/library/coloring.py | 52 ++-- src/calibre/library/comments.py | 6 +- src/calibre/library/custom_columns.py | 170 ++++++------ src/calibre/library/database.py | 6 +- src/calibre/library/database2.py | 65 +++-- src/calibre/library/field_metadata.py | 14 +- src/calibre/library/prefs.py | 4 +- src/calibre/library/restore.py | 8 +- src/calibre/library/save_to_disk.py | 2 +- src/calibre/library/schema_upgrades.py | 115 +++++---- src/calibre/library/sqlite.py | 2 +- src/calibre/linux.py | 116 ++++----- src/calibre/ptempfile.py | 4 +- src/calibre/spell/__init__.py | 2 +- src/calibre/spell/dictionary.py | 14 +- src/calibre/spell/import_from.py | 8 +- src/calibre/srv/ajax.py | 16 +- src/calibre/srv/auth.py | 15 +- src/calibre/srv/books.py | 6 +- src/calibre/srv/cdb.py | 4 +- src/calibre/srv/changes.py | 5 +- src/calibre/srv/code.py | 16 +- src/calibre/srv/content.py | 17 +- src/calibre/srv/http_request.py | 6 +- src/calibre/srv/http_response.py | 26 +- src/calibre/srv/jobs.py | 2 +- src/calibre/srv/legacy.py | 9 +- src/calibre/srv/loop.py | 13 +- src/calibre/srv/metadata.py | 6 +- src/calibre/srv/opds.py | 29 +-- src/calibre/srv/routes.py | 10 +- src/calibre/srv/standalone.py | 4 +- src/calibre/srv/tests/ajax.py | 2 +- src/calibre/srv/tests/auth.py | 2 +- src/calibre/srv/tests/content.py | 8 +- src/calibre/srv/tests/http.py | 2 +- src/calibre/srv/tests/loop.py | 3 +- src/calibre/srv/utils.py | 4 +- src/calibre/srv/web_socket.py | 12 +- src/calibre/startup.py | 2 +- src/calibre/test_build.py | 12 +- src/calibre/utils/bibtex.py | 4 +- src/calibre/utils/cleantext.py | 2 +- src/calibre/utils/config.py | 5 +- src/calibre/utils/config_base.py | 10 +- src/calibre/utils/exim.py | 22 +- src/calibre/utils/filenames.py | 12 +- src/calibre/utils/fonts/free_type.py | 4 +- src/calibre/utils/fonts/scanner.py | 10 +- src/calibre/utils/fonts/sfnt/cff/dict_data.py | 3 +- src/calibre/utils/fonts/sfnt/cff/table.py | 2 +- src/calibre/utils/fonts/sfnt/common.py | 12 +- src/calibre/utils/fonts/sfnt/container.py | 2 +- src/calibre/utils/fonts/sfnt/gsub.py | 3 +- src/calibre/utils/fonts/sfnt/head.py | 8 +- src/calibre/utils/fonts/sfnt/kern.py | 2 +- src/calibre/utils/fonts/sfnt/maxp.py | 3 +- src/calibre/utils/fonts/sfnt/metrics.py | 2 +- src/calibre/utils/fonts/sfnt/subset.py | 16 +- src/calibre/utils/fonts/utils.py | 10 +- src/calibre/utils/fonts/win_fonts.py | 12 +- src/calibre/utils/formatter_functions.py | 16 +- src/calibre/utils/https.py | 2 +- src/calibre/utils/icu_test.py | 2 +- src/calibre/utils/img.py | 14 +- src/calibre/utils/inotify.py | 2 +- src/calibre/utils/ipc/launch.py | 2 +- src/calibre/utils/ipc/pool.py | 2 +- src/calibre/utils/ipc/simple_worker.py | 2 +- src/calibre/utils/iphlpapi.py | 4 +- src/calibre/utils/linux_trash.py | 8 +- src/calibre/utils/localization.py | 7 +- src/calibre/utils/localunzip.py | 7 +- src/calibre/utils/matcher.py | 4 +- src/calibre/utils/mdns.py | 2 +- src/calibre/utils/mreplace.py | 2 +- src/calibre/utils/open_with/linux.py | 2 +- src/calibre/utils/podofo/__init__.py | 2 +- src/calibre/utils/rapydscript.py | 3 +- src/calibre/utils/recycle_bin.py | 4 +- src/calibre/utils/run_tests.py | 8 +- src/calibre/utils/seven_zip.py | 2 +- src/calibre/utils/shared_file.py | 2 +- src/calibre/utils/smartypants.py | 36 +-- src/calibre/utils/smtp.py | 4 +- src/calibre/utils/smtplib.py | 6 +- src/calibre/utils/speedups.py | 2 +- src/calibre/utils/threadpool.py | 5 +- src/calibre/utils/titlecase.py | 22 +- src/calibre/utils/unrar.py | 2 +- src/calibre/utils/webengine.py | 3 +- src/calibre/utils/winreg/dde.py | 4 +- src/calibre/utils/winreg/default_programs.py | 18 +- src/calibre/utils/winreg/lib.py | 4 +- src/calibre/utils/wmf/emf.py | 2 +- src/calibre/utils/zipfile.py | 27 +- src/calibre/web/feeds/__init__.py | 30 +-- src/calibre/web/feeds/news.py | 26 +- src/calibre/web/feeds/recipes/__init__.py | 2 +- src/calibre/web/feeds/recipes/collection.py | 16 +- src/calibre/web/feeds/recipes/model.py | 2 +- src/calibre/web/feeds/templates.py | 6 +- src/calibre/web/fetch/simple.py | 2 +- src/calibre/web/site_parsers/nytimes.py | 4 +- src/odf/odf2moinmoin.py | 8 +- src/odf/odf2xhtml.py | 4 +- 522 files changed, 2908 insertions(+), 3179 deletions(-) diff --git a/icons/icns/make_iconsets.py b/icons/icns/make_iconsets.py index aff3f2fa72..36cb55e263 100644 --- a/icons/icns/make_iconsets.py +++ b/icons/icns/make_iconsets.py @@ -27,7 +27,7 @@ for name, src in sources.items(): try: for sz in (16, 32, 128, 256, 512, 1024): iname = f'icon_{sz}x{sz}.png' - iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2) + iname2x = f'icon_{sz // 2}x{sz // 2}@2x.png' if src.endswith('.svg'): subprocess.check_call(['rsvg-convert', src, '-w', str(sz), '-h', str(sz), '-o', iname]) else: diff --git a/setup/build.py b/setup/build.py index db3f567cde..44b3abf01f 100644 --- a/setup/build.py +++ b/setup/build.py @@ -656,7 +656,7 @@ class Build(Command): os.chdir(bdir) try: self.check_call(cmd + ['-S', os.path.dirname(sources[0])]) - self.check_call([self.env.make] + ['-j{}'.format(cpu_count or 1)]) + self.check_call([self.env.make] + [f'-j{cpu_count or 1}']) finally: os.chdir(cwd) os.rename(self.j(bdir, 'libheadless.so'), target) @@ -733,7 +733,7 @@ sip-file = {os.path.basename(sipf)!r} env = os.environ.copy() if is_macos_universal_build: env['ARCHS'] = 'x86_64 arm64' - self.check_call([self.env.make] + ([] if iswindows else ['-j{}'.format(os.cpu_count() or 1)]), env=env) + self.check_call([self.env.make] + ([] if iswindows else [f'-j{os.cpu_count() or 1}']), env=env) e = 'pyd' if iswindows else 'so' m = glob.glob(f'{ext.name}/{ext.name}.*{e}') if not m: diff --git a/setup/check.py b/setup/check.py index c4bed18298..55330fa542 100644 --- a/setup/check.py +++ b/setup/check.py @@ -114,7 +114,7 @@ class Check(Command): for i, f in enumerate(dirty_files): self.info('\tChecking', f) if self.file_has_errors(f): - self.info('{} files left to check'.format(len(dirty_files) - i - 1)) + self.info(f'{len(dirty_files) - i - 1} files left to check') try: edit_file(f) except FileNotFoundError: diff --git a/setup/hosting.py b/setup/hosting.py index d7a265959b..7ce85ced49 100644 --- a/setup/hosting.py +++ b/setup/hosting.py @@ -56,16 +56,16 @@ class ReadFileWithProgressReporting: # {{{ eta = int((self._total - self.tell()) / bit_rate) + 1 eta_m, eta_s = eta / 60, eta % 60 sys.stdout.write( - ' {:.1f}% {:.1f}/{:.1f}MB {:.1f} KB/sec {} minutes, {} seconds left' - .format(frac * 100, mb_pos, mb_tot, kb_rate, eta_m, eta_s) + f' {frac * 100:.1f}% {mb_pos:.1f}/{mb_tot:.1f}MB {kb_rate:.1f} KB/sec {eta_m} minutes, {eta_s} seconds left' + ) sys.stdout.write('\x1b[u') if self.tell() >= self._total: sys.stdout.write('\n') t = int(time.time() - self.start_time) + 1 print( - 'Upload took {} minutes and {} seconds at {:.1f} KB/sec' - .format(t/60, t % 60, kb_rate) + f'Upload took {t/60} minutes and {t % 60} seconds at {kb_rate:.1f} KB/sec' + ) sys.stdout.flush() diff --git a/setup/install.py b/setup/install.py index b15ee0b532..e9e14f9184 100644 --- a/setup/install.py +++ b/setup/install.py @@ -388,7 +388,7 @@ class Bootstrap(Command): st = time.time() clone_cmd.insert(2, '--depth=1') subprocess.check_call(clone_cmd, cwd=self.d(self.SRC)) - print('Downloaded translations in {} seconds'.format(int(time.time() - st))) + print(f'Downloaded translations in {int(time.time() - st)} seconds') else: if os.path.exists(tdir): subprocess.check_call(['git', 'pull'], cwd=tdir) diff --git a/setup/plugins_mirror.py b/setup/plugins_mirror.py index 019b508bbd..b6e97b03f3 100644 --- a/setup/plugins_mirror.py +++ b/setup/plugins_mirror.py @@ -460,7 +460,7 @@ def plugin_to_index(plugin, count): released = datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).strftime('%e %b, %Y').lstrip() details = [ 'Version: {}'.format(escape('.'.join(map(str, plugin['version'])))), - 'Released: {}'.format(escape(released)), + f'Released: {escape(released)}', 'Author: {}'.format(escape(plugin['author'])), 'calibre: {}'.format(escape('.'.join(map(str, plugin['minimum_calibre_version'])))), 'Platforms: {}'.format(escape(', '.join(sorted(plugin['supported_platforms']) or ['all']))), diff --git a/setup/publish.py b/setup/publish.py index 69a213f10c..f6073461f3 100644 --- a/setup/publish.py +++ b/setup/publish.py @@ -50,9 +50,9 @@ class Stage2(Command): platforms = 'linux64', 'linuxarm64', 'osx', 'win' for x in platforms: cmd = ( - '''{exe} -c "import subprocess; subprocess.Popen(['{exe}', './setup.py', '{x}']).wait() != 0 and''' - ''' input('Build of {x} failed, press Enter to exit');"''' - ).format(exe=sys.executable, x=x) + f'''{sys.executable} -c "import subprocess; subprocess.Popen(['{sys.executable}', './setup.py', '{x}']).wait() != 0 and''' + f''' input('Build of {x} failed, press Enter to exit');"''' + ) session.append('title ' + x) session.append('launch ' + cmd) @@ -220,8 +220,8 @@ class Manual(Command): if x and not os.path.exists(x): os.symlink('.', x) self.info( - 'Built manual for {} languages in {} minutes' - .format(len(jobs), int((time.time() - st) / 60.)) + f'Built manual for {len(jobs)} languages in {int((time.time() - st) / 60.)} minutes' + ) finally: os.chdir(cwd) @@ -335,6 +335,6 @@ class TagRelease(Command): def run(self, opts): self.info('Tagging release') subprocess.check_call( - 'git tag -s v{0} -m "version-{0}"'.format(__version__).split() + f'git tag -s v{__version__} -m "version-{__version__}"'.split() ) subprocess.check_call(f'git push origin v{__version__}'.split()) diff --git a/setup/upload.py b/setup/upload.py index 487461104e..c2ac8fd6cb 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -361,12 +361,11 @@ class UploadDemo(Command): # {{{ def run(self, opts): check_call( - '''ebook-convert {}/demo.html /tmp/html2lrf.lrf ''' + f'''ebook-convert {self.j(self.SRC, HTML2LRF)}/demo.html /tmp/html2lrf.lrf ''' '''--title='Demonstration of html2lrf' --authors='Kovid Goyal' ''' '''--header ''' '''--serif-family "/usr/share/fonts/corefonts, Times New Roman" ''' - '''--mono-family "/usr/share/fonts/corefonts, Andale Mono" ''' - ''''''.format(self.j(self.SRC, HTML2LRF)), + '''--mono-family "/usr/share/fonts/corefonts, Andale Mono" ''', shell=True ) diff --git a/setup/win-ci.py b/setup/win-ci.py index 6b00d3bdaa..26e8c77acc 100644 --- a/setup/win-ci.py +++ b/setup/win-ci.py @@ -98,7 +98,7 @@ def main(): else: if len(sys.argv) == 1: raise SystemExit('Usage: win-ci.py sw|build|test') - raise SystemExit('{!r} is not a valid action'.format(sys.argv[-1])) + raise SystemExit(f'{sys.argv[-1]!r} is not a valid action') if __name__ == '__main__': diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index d7a69bfe19..8a9c97974b 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -292,7 +292,7 @@ def get_proxy_info(proxy_scheme, proxy_string): ''' from polyglot.urllib import urlparse try: - proxy_url = '%s://%s'%(proxy_scheme, proxy_string) + proxy_url = f'{proxy_scheme}://{proxy_string}' urlinfo = urlparse(proxy_url) ans = { 'scheme': urlinfo.scheme, diff --git a/src/calibre/constants.py b/src/calibre/constants.py index e94c6ff7ea..0827dcdc19 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -146,7 +146,7 @@ def _get_cache_dir(): if iswindows: try: - candidate = os.path.join(winutil.special_folder_path(winutil.CSIDL_LOCAL_APPDATA), '%s-cache'%__appname__) + candidate = os.path.join(winutil.special_folder_path(winutil.CSIDL_LOCAL_APPDATA), f'{__appname__}-cache') except ValueError: return confcache elif ismacos: @@ -341,7 +341,7 @@ class Plugins(collections.abc.Mapping): try: return import_module('calibre_extensions.' + name), '' except ModuleNotFoundError: - raise KeyError('No plugin named %r'%name) + raise KeyError(f'No plugin named {name!r}') except Exception as err: return None, str(err) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 7769ae5ea7..568267b6be 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -322,8 +322,7 @@ class Plugin: # {{{ interface. It is called when the user does: calibre-debug -r "Plugin Name". Any arguments passed are present in the args variable. ''' - raise NotImplementedError('The %s plugin has no command line interface' - %self.name) + raise NotImplementedError(f'The {self.name} plugin has no command line interface') # }}} @@ -540,7 +539,7 @@ class CatalogPlugin(Plugin): # {{{ Custom fields sort after standard fields ''' if key.startswith('#'): - return '~%s' % key[1:] + return f'~{key[1:]}' else: return key @@ -575,9 +574,8 @@ class CatalogPlugin(Plugin): # {{{ if requested_fields - all_fields: from calibre.library import current_library_name invalid_fields = sorted(requested_fields - all_fields) - print('invalid --fields specified: %s' % ', '.join(invalid_fields)) - print("available fields in '%s': %s" % - (current_library_name(), ', '.join(sorted(all_fields)))) + print('invalid --fields specified: {}'.format(', '.join(invalid_fields))) + print("available fields in '{}': {}".format(current_library_name(), ', '.join(sorted(all_fields)))) raise ValueError('unable to generate catalog with specified fields') fields = [x for x in of if x in all_fields] diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index 54e61dc24a..25e6990079 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -78,10 +78,9 @@ class OptionRecommendation: def validate_parameters(self): if self.option.choices and self.recommended_value not in \ self.option.choices: - raise ValueError('OpRec: %s: Recommended value not in choices'% - self.option.name) + raise ValueError(f'OpRec: {self.option.name}: Recommended value not in choices') if not (isinstance(self.recommended_value, (numbers.Number, bytes, str)) or self.recommended_value is None): - raise ValueError('OpRec: %s:'%self.option.name + repr( + raise ValueError(f'OpRec: {self.option.name}:' + repr( self.recommended_value) + ' is not a string or a number') @@ -229,7 +228,7 @@ class InputFormatPlugin(Plugin): def __call__(self, stream, options, file_ext, log, accelerators, output_dir): try: - log('InputFormatPlugin: %s running'%self.name) + log(f'InputFormatPlugin: {self.name} running') if hasattr(stream, 'name'): log('on', stream.name) except: diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 3911c738e1..9e267382cc 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -85,7 +85,7 @@ def disable_plugin(plugin_or_name): if plugin is None: raise ValueError(f'No plugin named: {x} found') if not plugin.can_be_disabled: - raise ValueError('Plugin %s cannot be disabled'%x) + raise ValueError(f'Plugin {x} cannot be disabled') dp = config['disabled_plugins'] dp.add(x) config['disabled_plugins'] = dp @@ -199,7 +199,7 @@ def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'): try: nfp = plugin.run(nfp) or nfp except: - print('Running file type plugin %s failed with traceback:'%plugin.name, file=oe) + print(f'Running file type plugin {plugin.name} failed with traceback:', file=oe) traceback.print_exc(file=oe) sys.stdout, sys.stderr = oo, oe def x(j): @@ -526,10 +526,10 @@ def add_plugin(path_to_zip_file): plugin = load_plugin(path_to_zip_file) if plugin.name in builtin_names: raise NameConflict( - 'A builtin plugin with the name %r already exists' % plugin.name) + f'A builtin plugin with the name {plugin.name!r} already exists') if plugin.name in get_system_plugins(): raise NameConflict( - 'A system plugin with the name %r already exists' % plugin.name) + f'A system plugin with the name {plugin.name!r} already exists') plugin = initialize_plugin(plugin, path_to_zip_file, PluginInstallationType.EXTERNAL) plugins = config['plugins'] zfp = os.path.join(plugin_dir, plugin.name+'.zip') @@ -892,7 +892,7 @@ def main(args=sys.argv): name, custom = opts.customize_plugin, '' plugin = find_plugin(name.strip()) if plugin is None: - print('No plugin with the name %s exists'%name) + print(f'No plugin with the name {name} exists') return 1 customize_plugin(plugin, custom) if opts.enable_plugin is not None: diff --git a/src/calibre/customize/zipplugin.py b/src/calibre/customize/zipplugin.py index 72bb7da4c4..bd473673d8 100644 --- a/src/calibre/customize/zipplugin.py +++ b/src/calibre/customize/zipplugin.py @@ -296,14 +296,14 @@ class CalibrePluginFinder: def load(self, path_to_zip_file): if not os.access(path_to_zip_file, os.R_OK): - raise PluginNotFound('Cannot access %r'%path_to_zip_file) + raise PluginNotFound(f'Cannot access {path_to_zip_file!r}') with zipfile.ZipFile(path_to_zip_file) as zf: plugin_name = self._locate_code(zf, path_to_zip_file) try: ans = None - plugin_module = 'calibre_plugins.%s'%plugin_name + plugin_module = f'calibre_plugins.{plugin_name}' m = sys.modules.get(plugin_module, None) if m is not None: reload(m) @@ -315,8 +315,7 @@ class CalibrePluginFinder: obj.name != 'Trivial Plugin': plugin_classes.append(obj) if not plugin_classes: - raise InvalidPlugin('No plugin class found in %s:%s'%( - as_unicode(path_to_zip_file), plugin_name)) + raise InvalidPlugin(f'No plugin class found in {as_unicode(path_to_zip_file)}:{plugin_name}') if len(plugin_classes) > 1: plugin_classes.sort(key=lambda c:(getattr(c, '__module__', None) or '').count('.')) @@ -324,14 +323,12 @@ class CalibrePluginFinder: if ans.minimum_calibre_version > numeric_version: raise InvalidPlugin( - 'The plugin at %s needs a version of calibre >= %s' % - (as_unicode(path_to_zip_file), '.'.join(map(str, + 'The plugin at {} needs a version of calibre >= {}'.format(as_unicode(path_to_zip_file), '.'.join(map(str, ans.minimum_calibre_version)))) if platform not in ans.supported_platforms: raise InvalidPlugin( - 'The plugin at %s cannot be used on %s' % - (as_unicode(path_to_zip_file), platform)) + f'The plugin at {as_unicode(path_to_zip_file)} cannot be used on {platform}') return ans except: @@ -359,8 +356,7 @@ class CalibrePluginFinder: else: if self._identifier_pat.match(plugin_name) is None: raise InvalidPlugin( - 'The plugin at %r uses an invalid import name: %r' % - (path_to_zip_file, plugin_name)) + f'The plugin at {path_to_zip_file!r} uses an invalid import name: {plugin_name!r}') pynames = [x for x in names if x.endswith('.py')] @@ -394,9 +390,8 @@ class CalibrePluginFinder: break if '__init__' not in names: - raise InvalidPlugin(('The plugin in %r is invalid. It does not ' + raise InvalidPlugin(f'The plugin in {path_to_zip_file!r} is invalid. It does not ' 'contain a top-level __init__.py file') - % path_to_zip_file) with self._lock: self.loaded_plugins[plugin_name] = path_to_zip_file, names, tuple(all_names) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 592bbfd6db..29b789b5f9 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -163,7 +163,7 @@ class DBPrefs(dict): # {{{ self.__setitem__(key, val) def get_namespaced(self, namespace, key, default=None): - key = 'namespaced:%s:%s'%(namespace, key) + key = f'namespaced:{namespace}:{key}' try: return dict.__getitem__(self, key) except KeyError: @@ -174,7 +174,7 @@ class DBPrefs(dict): # {{{ raise KeyError('Colons are not allowed in keys') if ':' in namespace: raise KeyError('Colons are not allowed in the namespace') - key = 'namespaced:%s:%s'%(namespace, key) + key = f'namespaced:{namespace}:{key}' self[key] = val def write_serialized(self, library_path): @@ -273,7 +273,7 @@ def IdentifiersConcat(): '''String concatenation aggregator for the identifiers map''' def step(ctxt, key, val): - ctxt.append('%s:%s'%(key, val)) + ctxt.append(f'{key}:{val}') def finalize(ctxt): try: @@ -684,7 +684,7 @@ class DB: suffix = 1 while icu_lower(cat + str(suffix)) in catmap: suffix += 1 - prints('Renaming user category %s to %s'%(cat, cat+str(suffix))) + prints(f'Renaming user category {cat} to {cat+str(suffix)}') user_cats[cat + str(suffix)] = user_cats[cat] del user_cats[cat] cats_changed = True @@ -700,7 +700,7 @@ class DB: for num, label in self.conn.get( 'SELECT id,label FROM custom_columns WHERE mark_for_delete=1'): table, lt = self.custom_table_names(num) - self.execute('''\ + self.execute(f'''\ DROP INDEX IF EXISTS {table}_idx; DROP INDEX IF EXISTS {lt}_aidx; DROP INDEX IF EXISTS {lt}_bidx; @@ -714,7 +714,7 @@ class DB: DROP VIEW IF EXISTS tag_browser_filtered_{table}; DROP TABLE IF EXISTS {table}; DROP TABLE IF EXISTS {lt}; - '''.format(table=table, lt=lt) + ''' ) self.prefs.set('update_all_last_mod_dates_on_start', True) self.deleted_fields.append('#'+label) @@ -764,16 +764,15 @@ class DB: # Create Foreign Key triggers if data['normalized']: - trigger = 'DELETE FROM %s WHERE book=OLD.id;'%lt + trigger = f'DELETE FROM {lt} WHERE book=OLD.id;' else: - trigger = 'DELETE FROM %s WHERE book=OLD.id;'%table + trigger = f'DELETE FROM {table} WHERE book=OLD.id;' triggers.append(trigger) if remove: with self.conn: for data in remove: - prints('WARNING: Custom column %r not found, removing.' % - data['label']) + prints('WARNING: Custom column {!r} not found, removing.'.format(data['label'])) self.execute('DELETE FROM custom_columns WHERE id=?', (data['num'],)) @@ -783,9 +782,9 @@ class DB: CREATE TEMP TRIGGER custom_books_delete_trg AFTER DELETE ON books BEGIN - %s + {} END; - '''%(' \n'.join(triggers))) + '''.format(' \n'.join(triggers))) # Setup data adapters def adapt_text(x, d): @@ -1212,7 +1211,7 @@ class DB: if re.match(r'^\w*$', label) is None or not label[0].isalpha() or label.lower() != label: raise ValueError(_('The label must contain only lower case letters, digits and underscores, and start with a letter')) if datatype not in CUSTOM_DATA_TYPES: - raise ValueError('%r is not a supported data type'%datatype) + raise ValueError(f'{datatype!r} is not a supported data type') normalized = datatype not in ('datetime', 'comments', 'int', 'bool', 'float', 'composite') is_multiple = is_multiple and datatype in ('text', 'composite') @@ -1241,29 +1240,29 @@ class DB: else: s_index = '' lines = [ - '''\ - CREATE TABLE %s( + f'''\ + CREATE TABLE {table}( id INTEGER PRIMARY KEY AUTOINCREMENT, - value %s NOT NULL %s, + value {dt} NOT NULL {collate}, link TEXT NOT NULL DEFAULT "", UNIQUE(value)); - '''%(table, dt, collate), + ''', - 'CREATE INDEX %s_idx ON %s (value %s);'%(table, table, collate), + f'CREATE INDEX {table}_idx ON {table} (value {collate});', - '''\ - CREATE TABLE %s( + f'''\ + CREATE TABLE {lt}( id INTEGER PRIMARY KEY AUTOINCREMENT, book INTEGER NOT NULL, value INTEGER NOT NULL, - %s + {s_index} UNIQUE(book, value) - );'''%(lt, s_index), + );''', - 'CREATE INDEX %s_aidx ON %s (value);'%(lt,lt), - 'CREATE INDEX %s_bidx ON %s (book);'%(lt,lt), + f'CREATE INDEX {lt}_aidx ON {lt} (value);', + f'CREATE INDEX {lt}_bidx ON {lt} (book);', - '''\ + f'''\ CREATE TRIGGER fkc_update_{lt}_a BEFORE UPDATE OF book ON {lt} BEGIN @@ -1324,22 +1323,22 @@ class DB: value AS sort FROM {table}; - '''.format(lt=lt, table=table), + ''', ] else: lines = [ - '''\ - CREATE TABLE %s( + f'''\ + CREATE TABLE {table}( id INTEGER PRIMARY KEY AUTOINCREMENT, book INTEGER, - value %s NOT NULL %s, + value {dt} NOT NULL {collate}, UNIQUE(book)); - '''%(table, dt, collate), + ''', - 'CREATE INDEX %s_idx ON %s (book);'%(table, table), + f'CREATE INDEX {table}_idx ON {table} (book);', - '''\ + f'''\ CREATE TRIGGER fkc_insert_{table} BEFORE INSERT ON {table} BEGIN @@ -1356,7 +1355,7 @@ class DB: THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; - '''.format(table=table), + ''', ] script = ' \n'.join(lines) self.execute(script) @@ -2396,15 +2395,14 @@ class DB: data = [] if highlight_start is not None and highlight_end is not None: if snippet_size is not None: - text = "snippet({fts_table}, 0, ?, ?, '…', {snippet_size})".format( - fts_table=fts_table, snippet_size=max(1, min(snippet_size, 64))) + text = f"snippet({fts_table}, 0, ?, ?, '…', {max(1, min(snippet_size, 64))})" else: text = f'highlight({fts_table}, 0, ?, ?)' data.append(highlight_start) data.append(highlight_end) query = 'SELECT {0}.id, {0}.book, {0}.format, {0}.user_type, {0}.user, {0}.annot_data, {1} FROM {0} ' query = query.format('annotations', text) - query += ' JOIN {fts_table} ON annotations.id = {fts_table}.rowid'.format(fts_table=fts_table) + query += f' JOIN {fts_table} ON annotations.id = {fts_table}.rowid' query += f' WHERE {fts_table} MATCH ?' data.append(fts_engine_query) if restrict_to_user: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 1c7193993b..285ad41c13 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -916,7 +916,7 @@ class Cache: try: return frozenset(self.fields[field].table.id_map.values()) except AttributeError: - raise ValueError('%s is not a many-one or many-many field' % field) + raise ValueError(f'{field} is not a many-one or many-many field') @read_api def get_usage_count_by_id(self, field): @@ -925,7 +925,7 @@ class Cache: try: return {k:len(v) for k, v in iteritems(self.fields[field].table.col_book_map)} except AttributeError: - raise ValueError('%s is not a many-one or many-many field' % field) + raise ValueError(f'{field} is not a many-one or many-many field') @read_api def get_id_map(self, field): @@ -937,7 +937,7 @@ class Cache: except AttributeError: if field == 'title': return self.fields[field].table.book_col_map.copy() - raise ValueError('%s is not a many-one or many-many field' % field) + raise ValueError(f'{field} is not a many-one or many-many field') @read_api def get_item_name(self, field, item_id): @@ -2319,7 +2319,7 @@ class Cache: try: func = f.table.rename_item except AttributeError: - raise ValueError('Cannot rename items for one-one fields: %s' % field) + raise ValueError(f'Cannot rename items for one-one fields: {field}') moved_books = set() id_map = {} for item_id, new_name in item_id_to_new_name_map.items(): @@ -2705,7 +2705,7 @@ class Cache: if mi.authors: try: quathors = mi.authors[:20] # Too many authors causes parsing of the search expression to fail - query = ' and '.join('authors:"=%s"'%(a.replace('"', '')) for a in quathors) + query = ' and '.join('authors:"={}"'.format(a.replace('"', '')) for a in quathors) qauthors = mi.authors[20:] except ValueError: return identical_book_ids diff --git a/src/calibre/db/categories.py b/src/calibre/db/categories.py index df5829347e..9927f8d11f 100644 --- a/src/calibre/db/categories.py +++ b/src/calibre/db/categories.py @@ -60,8 +60,7 @@ class Tag: @property def string_representation(self): - return '%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, - self.category, self.original_categories) + return f'{self.name}:{self.count}:{self.id}:{self.state}:{self.category}:{self.original_categories}' def __str__(self): return self.string_representation diff --git a/src/calibre/db/cli/cmd_add.py b/src/calibre/db/cli/cmd_add.py index f8df3a12e4..afe3f4af93 100644 --- a/src/calibre/db/cli/cmd_add.py +++ b/src/calibre/db/cli/cmd_add.py @@ -400,7 +400,7 @@ the folder related options below. try: getattr(parser.values, option.dest).append(compile_rule(rule)) except Exception: - raise OptionValueError('%r is not a valid filename pattern' % value) + raise OptionValueError(f'{value!r} is not a valid filename pattern') g.add_option( '-1', diff --git a/src/calibre/db/cli/cmd_add_custom_column.py b/src/calibre/db/cli/cmd_add_custom_column.py index 300d49bf6f..7873bf6af7 100644 --- a/src/calibre/db/cli/cmd_add_custom_column.py +++ b/src/calibre/db/cli/cmd_add_custom_column.py @@ -72,7 +72,7 @@ def do_add_custom_column(db, label, name, datatype, is_multiple, display): num = db.create_custom_column( label, name, datatype, is_multiple, display=display ) - prints('Custom column created with id: %s' % num) + prints(f'Custom column created with id: {num}') def main(opts, args, dbctx): diff --git a/src/calibre/db/cli/cmd_list_categories.py b/src/calibre/db/cli/cmd_list_categories.py index b474beaed2..0c5e7742a1 100644 --- a/src/calibre/db/cli/cmd_list_categories.py +++ b/src/calibre/db/cli/cmd_list_categories.py @@ -156,7 +156,7 @@ def main(opts, args, dbctx): def fmtr(v): v = v or 0 - ans = '%.1f' % v + ans = f'{v:.1f}' if ans.endswith('.0'): ans = ans[:-2] return ans diff --git a/src/calibre/db/cli/cmd_remove_custom_column.py b/src/calibre/db/cli/cmd_remove_custom_column.py index 9558521424..10baf19ecc 100644 --- a/src/calibre/db/cli/cmd_remove_custom_column.py +++ b/src/calibre/db/cli/cmd_remove_custom_column.py @@ -61,7 +61,7 @@ def do_remove_custom_column(db, label, force): ' Use calibredb custom_columns to get a list of labels.' ) % label ) - prints('Column %r removed.' % label) + prints(f'Column {label!r} removed.') def main(opts, args, dbctx): diff --git a/src/calibre/db/fields.py b/src/calibre/db/fields.py index 0380734746..53696bade8 100644 --- a/src/calibre/db/fields.py +++ b/src/calibre/db/fields.py @@ -89,7 +89,7 @@ class Field: if tweaks['sort_dates_using_visible_fields']: fmt = None if name in {'timestamp', 'pubdate', 'last_modified'}: - fmt = tweaks['gui_%s_display_format' % name] + fmt = tweaks[f'gui_{name}_display_format'] elif self.metadata['is_custom']: fmt = self.metadata.get('display', {}).get('date_format', None) self._sort_key = partial(clean_date_for_sort, fmt=fmt) @@ -454,7 +454,7 @@ class OnDeviceField(OneToOneField): loc.append(_('Card A')) if b is not None: loc.append(_('Card B')) - return ', '.join(loc) + ((' (%s books)'%count) if count > 1 else '') + return ', '.join(loc) + ((f' ({count} books)') if count > 1 else '') def __iter__(self): return iter(()) diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index d96f033e3a..1f0c310ab9 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -263,7 +263,7 @@ def composite_getter(mi, field, dbref, book_id, cache, formatter, template_cache except Exception: import traceback traceback.print_exc() - return 'ERROR WHILE EVALUATING: %s' % field + return f'ERROR WHILE EVALUATING: {field}' return ret @@ -365,7 +365,7 @@ class ProxyMetadata(Metadata): try: return ga(self, '_cache')[field] except KeyError: - raise AttributeError('Metadata object has no attribute named: %r' % field) + raise AttributeError(f'Metadata object has no attribute named: {field!r}') def __setattr__(self, field, val, extra=None): cache = ga(self, '_cache') diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index b54e515bb2..5cbc738008 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -616,9 +616,9 @@ class LibraryDatabase: def set_custom_bulk_multiple(self, ids, add=[], remove=[], label=None, num=None, notify=False): data = self.backend.custom_field_metadata(label, num) if not data['editable']: - raise ValueError('Column %r is not editable'%data['label']) + raise ValueError('Column {!r} is not editable'.format(data['label'])) if data['datatype'] != 'text' or not data['is_multiple']: - raise ValueError('Column %r is not text/multiple'%data['label']) + raise ValueError('Column {!r} is not text/multiple'.format(data['label'])) field = self.custom_field_name(label, num) self._do_bulk_modify(field, ids, add, remove, notify) @@ -756,7 +756,7 @@ class LibraryDatabase: if data['datatype'] == 'composite': return set() if not data['editable']: - raise ValueError('Column %r is not editable'%data['label']) + raise ValueError('Column {!r} is not editable'.format(data['label'])) if data['datatype'] == 'enumeration' and ( val and val not in data['display']['enum_values']): return set() @@ -789,7 +789,7 @@ class LibraryDatabase: val and val not in data['display']['enum_values']): return if not data['editable']: - raise ValueError('Column %r is not editable'%data['label']) + raise ValueError('Column {!r} is not editable'.format(data['label'])) if append: for book_id in ids: @@ -826,7 +826,7 @@ class LibraryDatabase: self.notify('cover', [book_id]) def original_fmt(self, book_id, fmt): - nfmt = ('ORIGINAL_%s'%fmt).upper() + nfmt = (f'ORIGINAL_{fmt}').upper() return nfmt if self.new_api.has_format(book_id, nfmt) else fmt def save_original_format(self, book_id, fmt, notify=True): @@ -931,7 +931,7 @@ for field in ( self.notify([book_id]) return ret if field == 'languages' else retval return func - setattr(LibraryDatabase, 'set_%s' % field.replace('!', ''), setter(field)) + setattr(LibraryDatabase, 'set_{}'.format(field.replace('!', '')), setter(field)) for field in ('authors', 'tags', 'publisher'): def renamer(field): @@ -941,7 +941,7 @@ for field in ('authors', 'tags', 'publisher'): return id_map[old_id] return func fname = field[:-1] if field in {'tags', 'authors'} else field - setattr(LibraryDatabase, 'rename_%s' % fname, renamer(field)) + setattr(LibraryDatabase, f'rename_{fname}', renamer(field)) LibraryDatabase.update_last_modified = lambda self, book_ids, commit=False, now=None: self.new_api.update_last_modified(book_ids, now=now) @@ -954,7 +954,7 @@ for field in ('authors', 'tags', 'publisher', 'series'): return self.new_api.all_field_names(field) return func name = field[:-1] if field in {'authors', 'tags'} else field - setattr(LibraryDatabase, 'all_%s_names' % name, getter(field)) + setattr(LibraryDatabase, f'all_{name}_names', getter(field)) LibraryDatabase.all_formats = lambda self: self.new_api.all_field_names('formats') LibraryDatabase.all_custom = lambda self, label=None, num=None:self.new_api.all_field_names(self.custom_field_name(label, num)) @@ -977,7 +977,7 @@ for field in ('tags', 'series', 'publishers', 'ratings', 'languages'): def func(self): return [[tid, tag] for tid, tag in iteritems(self.new_api.get_id_map(fname))] return func - setattr(LibraryDatabase, 'get_%s_with_ids' % field, getter(field)) + setattr(LibraryDatabase, f'get_{field}_with_ids', getter(field)) for field in ('author', 'tag', 'series'): def getter(field): @@ -986,7 +986,7 @@ for field in ('author', 'tag', 'series'): def func(self, item_id): return self.new_api.get_item_name(field, item_id) return func - setattr(LibraryDatabase, '%s_name' % field, getter(field)) + setattr(LibraryDatabase, f'{field}_name', getter(field)) for field in ('publisher', 'series', 'tag'): def getter(field): @@ -995,7 +995,7 @@ for field in ('publisher', 'series', 'tag'): def func(self, item_id): self.new_api.remove_items(fname, (item_id,)) return func - setattr(LibraryDatabase, 'delete_%s_using_id' % field, getter(field)) + setattr(LibraryDatabase, f'delete_{field}_using_id', getter(field)) # }}} # Legacy field API {{{ diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index 1ddc75b364..c8d696ebff 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -111,13 +111,9 @@ class Restore(Thread): 'and were not fully restored:\n') for x in self.conflicting_custom_cols: ans += '\t#'+x+'\n' - ans += '\tused:\t%s, %s, %s, %s\n'%(self.custom_columns[x][1], - self.custom_columns[x][2], - self.custom_columns[x][3], - self.custom_columns[x][5]) + ans += f'\tused:\t{self.custom_columns[x][1]}, {self.custom_columns[x][2]}, {self.custom_columns[x][3]}, {self.custom_columns[x][5]}\n' for coldef in self.conflicting_custom_cols[x]: - ans += '\tother:\t%s, %s, %s, %s\n'%(coldef[1], coldef[2], - coldef[3], coldef[5]) + ans += f'\tother:\t{coldef[1]}, {coldef[2]}, {coldef[3]}, {coldef[5]}\n' if self.mismatched_dirs: ans += '\n\n' diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py index 1dd5a7d1d0..cd56aabe93 100644 --- a/src/calibre/db/schema_upgrades.py +++ b/src/calibre/db/schema_upgrades.py @@ -243,14 +243,14 @@ class SchemaUpgrade: def upgrade_version_8(self): 'Add Tag Browser views' def create_tag_browser_view(table_name, column_name): - self.db.execute(''' - DROP VIEW IF EXISTS tag_browser_{tn}; - CREATE VIEW tag_browser_{tn} AS SELECT + self.db.execute(f''' + DROP VIEW IF EXISTS tag_browser_{table_name}; + CREATE VIEW tag_browser_{table_name} AS SELECT id, name, - (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count - FROM {tn}; - '''.format(tn=table_name, cn=column_name)) + (SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count + FROM {table_name}; + ''') for tn in ('authors', 'tags', 'publishers', 'series'): cn = tn[:-1] @@ -280,28 +280,28 @@ class SchemaUpgrade: def upgrade_version_10(self): 'Add restricted Tag Browser views' def create_tag_browser_view(table_name, column_name, view_column_name): - script = (''' - DROP VIEW IF EXISTS tag_browser_{tn}; - CREATE VIEW tag_browser_{tn} AS SELECT + script = (f''' + DROP VIEW IF EXISTS tag_browser_{table_name}; + CREATE VIEW tag_browser_{table_name} AS SELECT id, - {vcn}, - (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count - FROM {tn}; - DROP VIEW IF EXISTS tag_browser_filtered_{tn}; - CREATE VIEW tag_browser_filtered_{tn} AS SELECT + {view_column_name}, + (SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count + FROM {table_name}; + DROP VIEW IF EXISTS tag_browser_filtered_{table_name}; + CREATE VIEW tag_browser_filtered_{table_name} AS SELECT id, - {vcn}, - (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE - {cn}={tn}.id AND books_list_filter(book)) count - FROM {tn}; - '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) + {view_column_name}, + (SELECT COUNT(books_{table_name}_link.id) FROM books_{table_name}_link WHERE + {column_name}={table_name}.id AND books_list_filter(book)) count + FROM {table_name}; + ''') self.db.execute(script) for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", - ('books_%s_link'%field['table'],), all=False) + ('books_{}_link'.format(field['table']),), all=False) if table is not None: create_tag_browser_view(field['table'], field['link_column'], field['column']) @@ -309,75 +309,74 @@ class SchemaUpgrade: 'Add average rating to tag browser views' def create_std_tag_browser_view(table_name, column_name, view_column_name, sort_column_name): - script = (''' - DROP VIEW IF EXISTS tag_browser_{tn}; - CREATE VIEW tag_browser_{tn} AS SELECT + script = (f''' + DROP VIEW IF EXISTS tag_browser_{table_name}; + CREATE VIEW tag_browser_{table_name} AS SELECT id, - {vcn}, - (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count, + {view_column_name}, + (SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count, (SELECT AVG(ratings.rating) - FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings - WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND + FROM books_{table_name}_link AS tl, books_ratings_link AS bl, ratings + WHERE tl.{column_name}={table_name}.id AND bl.book=tl.book AND ratings.id = bl.rating AND ratings.rating <> 0) avg_rating, - {scn} AS sort - FROM {tn}; - DROP VIEW IF EXISTS tag_browser_filtered_{tn}; - CREATE VIEW tag_browser_filtered_{tn} AS SELECT + {sort_column_name} AS sort + FROM {table_name}; + DROP VIEW IF EXISTS tag_browser_filtered_{table_name}; + CREATE VIEW tag_browser_filtered_{table_name} AS SELECT id, - {vcn}, - (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE - {cn}={tn}.id AND books_list_filter(book)) count, + {view_column_name}, + (SELECT COUNT(books_{table_name}_link.id) FROM books_{table_name}_link WHERE + {column_name}={table_name}.id AND books_list_filter(book)) count, (SELECT AVG(ratings.rating) - FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings - WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND + FROM books_{table_name}_link AS tl, books_ratings_link AS bl, ratings + WHERE tl.{column_name}={table_name}.id AND bl.book=tl.book AND ratings.id = bl.rating AND ratings.rating <> 0 AND books_list_filter(bl.book)) avg_rating, - {scn} AS sort - FROM {tn}; + {sort_column_name} AS sort + FROM {table_name}; - '''.format(tn=table_name, cn=column_name, - vcn=view_column_name, scn=sort_column_name)) + ''') self.db.execute(script) def create_cust_tag_browser_view(table_name, link_table_name): - script = ''' - DROP VIEW IF EXISTS tag_browser_{table}; - CREATE VIEW tag_browser_{table} AS SELECT + script = f''' + DROP VIEW IF EXISTS tag_browser_{table_name}; + CREATE VIEW tag_browser_{table_name} AS SELECT id, value, - (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count, + (SELECT COUNT(id) FROM {link_table_name} WHERE value={table_name}.id) count, (SELECT AVG(r.rating) - FROM {lt}, + FROM {link_table_name}, books_ratings_link AS bl, ratings AS r - WHERE {lt}.value={table}.id AND bl.book={lt}.book AND + WHERE {link_table_name}.value={table_name}.id AND bl.book={link_table_name}.book AND r.id = bl.rating AND r.rating <> 0) avg_rating, value AS sort - FROM {table}; + FROM {table_name}; - DROP VIEW IF EXISTS tag_browser_filtered_{table}; - CREATE VIEW tag_browser_filtered_{table} AS SELECT + DROP VIEW IF EXISTS tag_browser_filtered_{table_name}; + CREATE VIEW tag_browser_filtered_{table_name} AS SELECT id, value, - (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND + (SELECT COUNT({link_table_name}.id) FROM {link_table_name} WHERE value={table_name}.id AND books_list_filter(book)) count, (SELECT AVG(r.rating) - FROM {lt}, + FROM {link_table_name}, books_ratings_link AS bl, ratings AS r - WHERE {lt}.value={table}.id AND bl.book={lt}.book AND + WHERE {link_table_name}.value={table_name}.id AND bl.book={link_table_name}.book AND r.id = bl.rating AND r.rating <> 0 AND books_list_filter(bl.book)) avg_rating, value AS sort - FROM {table}; - '''.format(lt=link_table_name, table=table_name) + FROM {table_name}; + ''' self.db.execute(script) for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", - ('books_%s_link'%field['table'],), all=False) + ('books_{}_link'.format(field['table']),), all=False) if table is not None: create_std_tag_browser_view(field['table'], field['link_column'], field['column'], field['category_sort']) @@ -389,7 +388,7 @@ class SchemaUpgrade: for (table,) in db_tables: tables.append(table) for table in tables: - link_table = 'books_%s_link'%table + link_table = f'books_{table}_link' if table.startswith('custom_column_') and link_table in tables: create_cust_tag_browser_view(table, link_table) @@ -580,9 +579,9 @@ class SchemaUpgrade: INSERT INTO identifiers (book, val) SELECT id,isbn FROM books WHERE isbn; - ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "%s"; + ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "{}"; - '''%isoformat(DEFAULT_DATE, sep=' ') + '''.format(isoformat(DEFAULT_DATE, sep=' ')) # Sqlite does not support non constant default values in alter # statements self.db.execute(script) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 649fda2703..d18c80baad 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -108,7 +108,7 @@ class DateSearch: # {{{ self.local_today = {'_today', 'today', icu_lower(_('today'))} self.local_yesterday = {'_yesterday', 'yesterday', icu_lower(_('yesterday'))} self.local_thismonth = {'_thismonth', 'thismonth', icu_lower(_('thismonth'))} - self.daysago_pat = regex.compile(r'(%s|daysago|_daysago)$'%_('daysago'), flags=regex.UNICODE | regex.VERSION1) + self.daysago_pat = regex.compile(r'({}|daysago|_daysago)$'.format(_('daysago')), flags=regex.UNICODE | regex.VERSION1) def eq(self, dbdate, query, field_count): if dbdate.year == query.year: diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index 2cc83cb08a..51d5b8e802 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -72,7 +72,7 @@ class Table: self.unserialize = lambda x: x.replace('|', ',') if x else '' self.serialize = lambda x: x.replace(',', '|') self.link_table = (link_table if link_table else - 'books_%s_link'%self.metadata['table']) + 'books_{}_link'.format(self.metadata['table'])) if self.supports_notes and dt == 'rating': # custom ratings table self.supports_notes = False diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 19112f91ee..5f32d00f34 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -248,7 +248,7 @@ class AddRemoveTest(BaseTest): item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,), permanent=True) for x in (fmtpath, bookpath, authorpath): - af(os.path.exists(x), 'The file %s exists, when it should not' % x) + af(os.path.exists(x), f'The file {x} exists, when it should not') for c in (cache, self.init_cache()): table = c.fields['authors'].table self.assertNotIn(1, c.all_book_ids()) @@ -279,7 +279,7 @@ class AddRemoveTest(BaseTest): item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,)) for x in (fmtpath, bookpath, authorpath): - af(os.path.exists(x), 'The file %s exists, when it should not' % x) + af(os.path.exists(x), f'The file {x} exists, when it should not') b, f = cache.list_trash_entries() self.assertEqual(len(b), 1) self.assertEqual(len(f), 0) diff --git a/src/calibre/db/tests/base.py b/src/calibre/db/tests/base.py index 6af4ec2510..1a0d96bd75 100644 --- a/src/calibre/db/tests/base.py +++ b/src/calibre/db/tests/base.py @@ -48,7 +48,7 @@ class BaseTest(unittest.TestCase): def create_db(self, library_path): from calibre.library.database2 import LibraryDatabase2 if LibraryDatabase2.exists_at(library_path): - raise ValueError('A library already exists at %r'%library_path) + raise ValueError(f'A library already exists at {library_path!r}') src = os.path.join(os.path.dirname(__file__), 'metadata.db') dest = os.path.join(library_path, 'metadata.db') shutil.copyfile(src, dest) @@ -114,8 +114,8 @@ class BaseTest(unittest.TestCase): if isinstance(attr1, (tuple, list)) and 'authors' not in attr and 'languages' not in attr: attr1, attr2 = set(attr1), set(attr2) self.assertEqual(attr1, attr2, - '%s not the same: %r != %r'%(attr, attr1, attr2)) + f'{attr} not the same: {attr1!r} != {attr2!r}') if attr.startswith('#') and attr + '_index' not in exclude: attr1, attr2 = mi1.get_extra(attr), mi2.get_extra(attr) self.assertEqual(attr1, attr2, - '%s {#extra} not the same: %r != %r'%(attr, attr1, attr2)) + f'{attr} {{#extra}} not the same: {attr1!r} != {attr2!r}') diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index 86e157aa6f..793faf93f1 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -32,8 +32,7 @@ class ET: legacy = self.legacy or test.init_legacy(test.cloned_library) oldres = getattr(old, self.func_name)(*self.args, **self.kwargs) newres = getattr(legacy, self.func_name)(*self.args, **self.kwargs) - test.assertEqual(oldres, newres, 'Equivalence test for {} with args: {} and kwargs: {} failed'.format( - self.func_name, reprlib.repr(self.args), reprlib.repr(self.kwargs))) + test.assertEqual(oldres, newres, f'Equivalence test for {self.func_name} with args: {reprlib.repr(self.args)} and kwargs: {reprlib.repr(self.kwargs)} failed') self.retval = newres return newres diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py index f30eeaa81b..0f1226ce3d 100644 --- a/src/calibre/db/tests/reading.py +++ b/src/calibre/db/tests/reading.py @@ -165,10 +165,10 @@ class ReadingTest(BaseTest): x = list(reversed(order)) ae(order, cache.multisort([(field, True)], ids_to_sort=x), - 'Ascending sort of %s failed'%field) + f'Ascending sort of {field} failed') ae(x, cache.multisort([(field, False)], ids_to_sort=order), - 'Descending sort of %s failed'%field) + f'Descending sort of {field} failed') # Test sorting of is_multiple fields. @@ -337,8 +337,7 @@ class ReadingTest(BaseTest): for query, ans in iteritems(oldvals): nr = cache.search(query, '') self.assertEqual(ans, nr, - 'Old result: %r != New result: %r for search: %s'%( - ans, nr, query)) + f'Old result: {ans!r} != New result: {nr!r} for search: {query}') # Test searching by id, which was introduced in the new backend self.assertEqual(cache.search('id:1', ''), {1}) @@ -414,13 +413,12 @@ class ReadingTest(BaseTest): ): continue self.assertEqual(oval, nval, - 'The attribute %s for %s in category %s does not match. Old is %r, New is %r' - %(attr, old.name, category, oval, nval)) + f'The attribute {attr} for {old.name} in category {category} does not match. Old is {oval!r}, New is {nval!r}') for category in old_categories: old, new = old_categories[category], new_categories[category] self.assertEqual(len(old), len(new), - 'The number of items in the category %s is not the same'%category) + f'The number of items in the category {category} is not the same') for o, n in zip(old, new): compare_category(category, o, n) @@ -595,7 +593,7 @@ class ReadingTest(BaseTest): test(True, {3}, 'Unknown') c.limit = 5 for i in range(6): - test(False, set(), 'nomatch_%s' % i) + test(False, set(), f'nomatch_{i}') test(False, {3}, 'Unknown') # cached search expired test(False, {3}, '', 'unknown', num=1) test(True, {3}, '', 'unknown', num=1) @@ -638,7 +636,7 @@ class ReadingTest(BaseTest): v = pmi.get_standard_metadata(field) self.assertTrue(v is None or isinstance(v, dict)) self.assertEqual(f(mi.get_standard_metadata(field, False)), f(v), - 'get_standard_metadata() failed for field %s' % field) + f'get_standard_metadata() failed for field {field}') for field, meta in cache.field_metadata.custom_iteritems(): if meta['datatype'] != 'composite': self.assertEqual(f(getattr(mi, field)), f(getattr(pmi, field)), diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index 929b54b5e1..fe5b6b58aa 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -65,19 +65,16 @@ class WritingTest(BaseTest): if test.name.endswith('_index'): val = float(val) if val is not None else 1.0 self.assertEqual(sqlite_res, val, - 'Failed setting for %s with value %r, sqlite value not the same. val: %r != sqlite_val: %r'%( - test.name, val, val, sqlite_res)) + f'Failed setting for {test.name} with value {val!r}, sqlite value not the same. val: {val!r} != sqlite_val: {sqlite_res!r}') else: test.setter(db)(1, val) old_cached_res = getter(1) self.assertEqual(old_cached_res, cached_res, - 'Failed setting for %s with value %r, cached value not the same. Old: %r != New: %r'%( - test.name, val, old_cached_res, cached_res)) + f'Failed setting for {test.name} with value {val!r}, cached value not the same. Old: {old_cached_res!r} != New: {cached_res!r}') db.refresh() old_sqlite_res = getter(1) self.assertEqual(old_sqlite_res, sqlite_res, - 'Failed setting for %s, sqlite value not the same: %r != %r'%( - test.name, old_sqlite_res, sqlite_res)) + f'Failed setting for {test.name}, sqlite value not the same: {old_sqlite_res!r} != {sqlite_res!r}') del db # }}} @@ -755,7 +752,7 @@ class WritingTest(BaseTest): self.assertEqual(ldata, {aid:d['link'] for aid, d in iteritems(c.author_data())}) self.assertEqual({3}, cache.set_link_for_authors({aid:'xxx' if aid == max(adata) else str(aid) for aid in adata}), 'Setting the author link to the same value as before, incorrectly marked some books as dirty') - sdata = {aid:'%s, changed' % aid for aid in adata} + sdata = {aid:f'{aid}, changed' for aid in adata} self.assertEqual({1,2,3}, cache.set_sort_for_authors(sdata)) for bid in (1, 2, 3): self.assertIn(', changed', cache.field_for('author_sort', bid)) diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py index d35067af72..13e3d692f5 100644 --- a/src/calibre/db/utils.py +++ b/src/calibre/db/utils.py @@ -294,7 +294,7 @@ class ThumbnailCache: if not hasattr(self, 'total_size'): self._load_index() self._invalidate_sizes() - ts = ('%.2f' % timestamp).replace('.00', '') + 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]) diff --git a/src/calibre/db/view.py b/src/calibre/db/view.py index ade2131ecf..b047360839 100644 --- a/src/calibre/db/view.py +++ b/src/calibre/db/view.py @@ -95,7 +95,7 @@ def format_is_multiple(x, sep=',', repl=None): def format_identifiers(x): if not x: return None - return ','.join('%s:%s'%(k, v) for k, v in iteritems(x)) + return ','.join(f'{k}:{v}' for k, v in iteritems(x)) class View: @@ -190,7 +190,7 @@ class View: def _get_id(self, idx, index_is_id=True): if index_is_id and not self.cache.has_id(idx): - raise IndexError('No book with id %s present'%idx) + raise IndexError(f'No book with id {idx} present') return idx if index_is_id else self.index_to_id(idx) def has_id(self, book_id): @@ -242,7 +242,7 @@ class View: def _get(self, field, idx, index_is_id=True, default_value=None, fmt=lambda x:x): id_ = idx if index_is_id else self.index_to_id(idx) if index_is_id and not self.cache.has_id(id_): - raise IndexError('No book with id %s present'%idx) + raise IndexError(f'No book with id {idx} present') return fmt(self.cache.field_for(field, id_, default_value=default_value)) def get_series_sort(self, idx, index_is_id=True, default_value=''): diff --git a/src/calibre/db/write.py b/src/calibre/db/write.py index 7d9065bc8a..ec704b5858 100644 --- a/src/calibre/db/write.py +++ b/src/calibre/db/write.py @@ -204,7 +204,7 @@ def one_one_in_books(book_id_val_map, db, field, *args): if book_id_val_map: sequence = ((sqlite_datetime(v), k) for k, v in book_id_val_map.items()) db.executemany( - 'UPDATE books SET %s=? WHERE id=?'%field.metadata['column'], sequence) + 'UPDATE books SET {}=? WHERE id=?'.format(field.metadata['column']), sequence) field.table.book_col_map.update(book_id_val_map) return set(book_id_val_map) @@ -229,13 +229,13 @@ def one_one_in_other(book_id_val_map, db, field, *args): book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) if v != g(k, missing)} deleted = tuple((k,) for k, v in iteritems(book_id_val_map) if v is None) if deleted: - db.executemany('DELETE FROM %s WHERE book=?'%field.metadata['table'], + db.executemany('DELETE FROM {} WHERE book=?'.format(field.metadata['table']), deleted) for book_id in deleted: field.table.book_col_map.pop(book_id[0], None) updated = {k:v for k, v in iteritems(book_id_val_map) if v is not None} if updated: - db.executemany('INSERT OR REPLACE INTO %s(book,%s) VALUES (?,?)'%( + db.executemany('INSERT OR REPLACE INTO {}(book,{}) VALUES (?,?)'.format( field.metadata['table'], field.metadata['column']), ((k, sqlite_datetime(v)) for k, v in iteritems(updated))) field.table.book_col_map.update(updated) @@ -260,7 +260,7 @@ def custom_series_index(book_id_val_map, db, field, *args): # sorts the same as other books with no series. field.table.remove_books((book_id,), db) if sequence: - db.executemany('UPDATE %s SET %s=? WHERE book=? AND value=?'%( + db.executemany('UPDATE {} SET {}=? WHERE book=? AND value=?'.format( field.metadata['table'], field.metadata['column']), sequence) return {s[1] for s in sequence} # }}} @@ -287,7 +287,7 @@ def get_db_id(val, db, m, table, kmap, rid_map, allow_case_change, db.execute('INSERT INTO authors(name,sort) VALUES (?,?)', (val.replace(',', '|'), aus)) else: - db.execute('INSERT INTO %s(%s) VALUES (?)'%( + db.execute('INSERT INTO {}({}) VALUES (?)'.format( m['table'], m['column']), (val,)) item_id = rid_map[kval] = db.last_insert_rowid() table.id_map[item_id] = val @@ -310,7 +310,7 @@ def change_case(case_changes, dirtied, db, table, m, is_authors=False): else: vals = ((val, item_id) for item_id, val in iteritems(case_changes)) db.executemany( - 'UPDATE %s SET %s=? WHERE id=?'%(m['table'], m['column']), vals) + 'UPDATE {} SET {}=? WHERE id=?'.format(m['table'], m['column']), vals) for item_id, val in iteritems(case_changes): table.id_map[item_id] = val dirtied.update(table.col_book_map[item_id]) @@ -366,7 +366,7 @@ def many_one(book_id_val_map, db, field, allow_case_change, *args): # Update the db link table if deleted: - db.executemany('DELETE FROM %s WHERE book=?'%table.link_table, + db.executemany(f'DELETE FROM {table.link_table} WHERE book=?', ((k,) for k in deleted)) if updated: sql = ( @@ -383,7 +383,7 @@ def many_one(book_id_val_map, db, field, allow_case_change, *args): if remove: if table.supports_notes: db.clear_notes_for_category_items(table.name, remove) - db.executemany('DELETE FROM %s WHERE id=?'%m['table'], + db.executemany('DELETE FROM {} WHERE id=?'.format(m['table']), ((item_id,) for item_id in remove)) for item_id in remove: del table.id_map[item_id] @@ -467,14 +467,14 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args): # Update the db link table if deleted: - db.executemany('DELETE FROM %s WHERE book=?'%table.link_table, + db.executemany(f'DELETE FROM {table.link_table} WHERE book=?', ((k,) for k in deleted)) if updated: vals = ( (book_id, val) for book_id, vals in iteritems(updated) for val in vals ) - db.executemany('DELETE FROM %s WHERE book=?'%table.link_table, + db.executemany(f'DELETE FROM {table.link_table} WHERE book=?', ((k,) for k in updated)) db.executemany('INSERT INTO {}(book,{}) VALUES(?, ?)'.format( table.link_table, m['link_column']), vals) @@ -488,7 +488,7 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args): if remove: if table.supports_notes: db.clear_notes_for_category_items(table.name, remove) - db.executemany('DELETE FROM %s WHERE id=?'%m['table'], + db.executemany('DELETE FROM {} WHERE id=?'.format(m['table']), ((item_id,) for item_id in remove)) for item_id in remove: del table.id_map[item_id] diff --git a/src/calibre/debug.py b/src/calibre/debug.py index ddadff37da..9d319c88e1 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -321,7 +321,7 @@ def main(args=sys.argv): elif ext in {'mobi', 'azw', 'azw3'}: inspect_mobi(path) else: - print('Cannot dump unknown filetype: %s' % path) + print(f'Cannot dump unknown filetype: {path}') elif len(args) >= 2 and os.path.exists(os.path.join(args[1], '__main__.py')): sys.path.insert(0, args[1]) run_script(os.path.join(args[1], '__main__.py'), args[2:]) diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index 317a30e4b4..a328aad63f 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -90,7 +90,7 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None, try: d.startup() except: - out('Startup failed for device plugin: %s'%d) + out(f'Startup failed for device plugin: {d}') if disabled_plugins is None: disabled_plugins = list(disabled_device_plugins()) diff --git a/src/calibre/devices/cli.py b/src/calibre/devices/cli.py index 2b7bbb633f..461185cfc5 100755 --- a/src/calibre/devices/cli.py +++ b/src/calibre/devices/cli.py @@ -208,7 +208,7 @@ def main(): try: d.startup() except: - print('Startup failed for device plugin: %s'%d) + print(f'Startup failed for device plugin: {d}') if d.MANAGES_DEVICE_PRESENCE: cd = d.detect_managed_devices(scanner.devices) if cd is not None: diff --git a/src/calibre/devices/cybook/driver.py b/src/calibre/devices/cybook/driver.py index 07ef10bb77..0c95b5f881 100644 --- a/src/calibre/devices/cybook/driver.py +++ b/src/calibre/devices/cybook/driver.py @@ -49,7 +49,7 @@ class CYBOOK(USBMS): coverdata = coverdata[2] else: coverdata = None - with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: + with open(f'{os.path.join(path, filename)}_6090.t2b', 'wb') as t2bfile: t2b.write_t2b(t2bfile, coverdata) fsync(t2bfile) @@ -89,7 +89,7 @@ class ORIZON(CYBOOK): coverdata = coverdata[2] else: coverdata = None - with open('%s.thn' % filepath, 'wb') as thnfile: + with open(f'{filepath}.thn', 'wb') as thnfile: t4b.write_t4b(thnfile, coverdata) fsync(thnfile) diff --git a/src/calibre/devices/iriver/driver.py b/src/calibre/devices/iriver/driver.py index 7beb9cf794..d6d4e5529b 100644 --- a/src/calibre/devices/iriver/driver.py +++ b/src/calibre/devices/iriver/driver.py @@ -28,9 +28,9 @@ class IRIVER_STORY(USBMS): VENDOR_NAME = 'IRIVER' WINDOWS_MAIN_MEM = ['STORY', 'STORY_EB05', 'STORY_WI-FI', 'STORY_EB07', 'STORY_EB12'] - WINDOWS_MAIN_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_MAIN_MEM))) + WINDOWS_MAIN_MEM = re.compile(r'({})&'.format('|'.join(WINDOWS_MAIN_MEM))) WINDOWS_CARD_A_MEM = ['STORY', 'STORY_SD', 'STORY_EB12_SD'] - WINDOWS_CARD_A_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_CARD_A_MEM))) + WINDOWS_CARD_A_MEM = re.compile(r'({})&'.format('|'.join(WINDOWS_CARD_A_MEM))) # OSX_MAIN_MEM = 'Kindle Internal Storage Media' # OSX_CARD_A_MEM = 'Kindle Card Storage Media' diff --git a/src/calibre/devices/kindle/apnx.py b/src/calibre/devices/kindle/apnx.py index 9321b1fd53..d8b911dc48 100644 --- a/src/calibre/devices/kindle/apnx.py +++ b/src/calibre/devices/kindle/apnx.py @@ -105,13 +105,13 @@ class APNXBuilder: # Updated header if we have a KF8 file... if apnx_meta['format'] == 'MOBI_8': - content_header = '{"contentGuid":"%(guid)s","asin":"%(asin)s","cdeType":"%(cdetype)s","format":"%(format)s","fileRevisionId":"1","acr":"%(acr)s"}' % apnx_meta # noqa: E501 + content_header = '{{"contentGuid":"{guid}","asin":"{asin}","cdeType":"{cdetype}","format":"{format}","fileRevisionId":"1","acr":"{acr}"}}'.format(**apnx_meta) # noqa: E501 else: # My 5.1.x Touch & 3.4 K3 seem to handle the 'extended' header fine for # legacy mobi files, too. But, since they still handle this one too, let's # try not to break old devices, and keep using the simple header ;). - content_header = '{"contentGuid":"%(guid)s","asin":"%(asin)s","cdeType":"%(cdetype)s","fileRevisionId":"1"}' % apnx_meta - page_header = '{"asin":"%(asin)s","pageMap":"' % apnx_meta + content_header = '{{"contentGuid":"{guid}","asin":"{asin}","cdeType":"{cdetype}","fileRevisionId":"1"}}'.format(**apnx_meta) + page_header = '{{"asin":"{asin}","pageMap":"'.format(**apnx_meta) page_header += pages.page_maps + '"}' if DEBUG: prints('APNX Content Header:', content_header) diff --git a/src/calibre/devices/kindle/bookmark.py b/src/calibre/devices/kindle/bookmark.py index 3104ddc5cd..1e013ac06f 100644 --- a/src/calibre/devices/kindle/bookmark.py +++ b/src/calibre/devices/kindle/bookmark.py @@ -33,7 +33,7 @@ class Bookmark: # {{{ def record(self, n): from calibre.ebooks.metadata.mobi import StreamSlicer if n >= self.nrecs: - raise ValueError('non-existent record %r' % n) + raise ValueError(f'non-existent record {n!r}') offoff = 78 + (8 * n) start, = unpack('>I', self.data[offoff + 0:offoff + 4]) stop = None @@ -141,7 +141,7 @@ class Bookmark: # {{{ # Search looks for book title match, highlight match, and location match # Author is not matched # This will find the first instance of a clipping only - book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format) + book_fs = self.path.replace(f'.{self.bookmark_extension}',f'.{self.book_format}') with open(book_fs,'rb') as f2: stream = io.BytesIO(f2.read()) mi = get_topaz_metadata(stream) @@ -152,7 +152,7 @@ class Bookmark: # {{{ with open(my_clippings, encoding='utf-8', errors='replace') as f2: marker_found = 0 text = '' - search_str1 = '%s' % (mi.title) + search_str1 = f'{mi.title}' search_str2 = '- Highlight Loc. %d' % (displayed_location) for line in f2: if marker_found == 0: @@ -271,12 +271,12 @@ class Bookmark: # {{{ self.last_read_location = self.last_read - self.pdf_page_offset else: - print('unsupported bookmark_extension: %s' % self.bookmark_extension) + print(f'unsupported bookmark_extension: {self.bookmark_extension}') self.user_notes = user_notes def get_book_length(self): from calibre.ebooks.metadata.mobi import StreamSlicer - book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format) + book_fs = self.path.replace(f'.{self.bookmark_extension}',f'.{self.book_format}') self.book_length = 0 if self.bookmark_extension == 'mbp': @@ -300,6 +300,6 @@ class Bookmark: # {{{ except: pass else: - print('unsupported bookmark_extension: %s' % self.bookmark_extension) + print(f'unsupported bookmark_extension: {self.bookmark_extension}') # }}} diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 6c3e08f698..904c459b06 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -294,7 +294,7 @@ class KINDLE(USBMS): typ=user_notes[location]['type'], text=(user_notes[location]['text'] if user_notes[location]['type'] == 'Note' else - '%s' % user_notes[location]['text']))) + '{}'.format(user_notes[location]['text'])))) else: if bookmark.book_format == 'pdf': annotations.append( @@ -351,7 +351,7 @@ class KINDLE(USBMS): bm.value.path, index_is_id=True) elif bm.type == 'kindle_clippings': # Find 'My Clippings' author=Kindle in database, or add - last_update = 'Last modified %s' % strftime('%x %X',bm.value['timestamp'].timetuple()) + last_update = 'Last modified {}'.format(strftime('%x %X',bm.value['timestamp'].timetuple())) mc_id = list(db.data.search_getting_ids('title:"My Clippings"', '', sort_results=False)) if mc_id: db.add_format_with_hooks(mc_id[0], 'TXT', bm.value['path'], @@ -623,7 +623,7 @@ class KINDLE2(KINDLE): except: pass - apnx_path = '%s.apnx' % os.path.join(path, filename) + apnx_path = f'{os.path.join(path, filename)}.apnx' apnx_builder = APNXBuilder() # Check to see if there is an existing apnx file on Kindle we should keep. if opts.extra_customization[self.OPT_APNX_OVERWRITE] or not os.path.exists(apnx_path): @@ -636,7 +636,7 @@ class KINDLE2(KINDLE): if temp in self.EXTRA_CUSTOMIZATION_CHOICES[self.OPT_APNX_METHOD]: method = temp else: - print('Invalid method choice for this book (%r), ignoring.' % temp) + print(f'Invalid method choice for this book ({temp!r}), ignoring.') except: print('Could not retrieve override method choice, using default.') apnx_builder.write_apnx(filepath, apnx_path, method=method, page_count=custom_page_count) diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py index 786ae89fef..451b5cea38 100644 --- a/src/calibre/devices/kobo/books.py +++ b/src/calibre/devices/kobo/books.py @@ -106,7 +106,7 @@ class Book(Book_): if self.contentID: fmt('Content ID', self.contentID) if self.kobo_series: - fmt('Kobo Series', self.kobo_series + ' #%s'%self.kobo_series_number) + fmt('Kobo Series', self.kobo_series + f' #{self.kobo_series_number}') if self.kobo_series_id: fmt('Kobo Series ID', self.kobo_series_id) if self.kobo_subtitle: @@ -203,7 +203,7 @@ class KTCollectionsBookList(CollectionsBookList): fm = None attr = attr.strip() if show_debug: - debug_print("KTCollectionsBookList:get_collections - attr='%s'"%attr) + debug_print(f"KTCollectionsBookList:get_collections - attr='{attr}'") # If attr is device_collections, then we cannot use # format_field, because we don't know the fields where the diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 5c376c0004..50632bb032 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -384,25 +384,25 @@ class KOBO(USBMS): if self.dbversion >= 33: query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, IsDownloaded from content where ' - 'BookID is Null %(previews)s %(recommendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict( + 'BookID is Null {previews} {recommendations} and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict( expiry=' and ContentType = 6)' if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')', previews=' and Accessibility <> 6' if not self.show_previews else '', - recommendations=" and IsDownloaded in ('true', 1)" if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] is False else '') + recommendations=" and IsDownloaded in ('true', 1)" if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] is False else '')) elif self.dbversion >= 16 and self.dbversion < 33: query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, "1" as IsDownloaded from content where ' - 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' - if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)' + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')) elif self.dbversion < 16 and self.dbversion >= 14: query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where ' - 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' - if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)' + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')) elif self.dbversion < 14 and self.dbversion >= 8: query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where ' - 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' - if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)' + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')) else: query = ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, ' @@ -608,12 +608,12 @@ class KOBO(USBMS): self.report_progress(1.0, _('Removing books from device metadata listing...')) def add_books_to_metadata(self, locations, metadata, booklists): - debug_print('KoboTouch::add_books_to_metadata - start. metadata=%s' % metadata[0]) + debug_print(f'KoboTouch::add_books_to_metadata - start. metadata={metadata[0]}') metadata = iter(metadata) for i, location in enumerate(locations): self.report_progress((i+1) / float(len(locations)), _('Adding books to device metadata listing...')) info = next(metadata) - debug_print('KoboTouch::add_books_to_metadata - info=%s' % info) + debug_print(f'KoboTouch::add_books_to_metadata - info={info}') blist = 2 if location[1] == 'cardb' else 1 if location[1] == 'carda' else 0 # Extract the correct prefix from the pathname. To do this correctly, @@ -645,7 +645,7 @@ class KOBO(USBMS): book.size = os.stat(self.normalize_path(path)).st_size b = booklists[blist].add_book(book, replace_metadata=True) if b: - debug_print('KoboTouch::add_books_to_metadata - have a new book - book=%s' % book) + debug_print(f'KoboTouch::add_books_to_metadata - have a new book - book={book}') b._new_book = True self.report_progress(1.0, _('Adding books to device metadata listing...')) @@ -755,9 +755,9 @@ class KOBO(USBMS): ' selecting "Configure this device" and then the ' ' "Attempt to support newer firmware" option.' ' Doing so may require you to perform a Factory reset of' - ' your Kobo.') + (( - '\nDevice database version: %s.' - '\nDevice firmware version: %s') % (self.dbversion, self.display_fwversion)) + ' your Kobo.') + ( + f'\nDevice database version: {self.dbversion}.' + f'\nDevice firmware version: {self.display_fwversion}') , UserFeedback.WARN) return False @@ -807,7 +807,7 @@ class KOBO(USBMS): ('card_a', 'metadata.calibre', 1), ('card_b', 'metadata.calibre', 2) ]: - prefix = getattr(self, '_%s_prefix'%prefix) + prefix = getattr(self, f'_{prefix}_prefix') if prefix is not None and os.path.exists(prefix): paths[source_id] = os.path.join(prefix, *(path.split('/'))) return paths @@ -891,7 +891,7 @@ class KOBO(USBMS): cursor.close() def update_device_database_collections(self, booklists, collections_attributes, oncard): - debug_print("Kobo:update_device_database_collections - oncard='%s'"%oncard) + debug_print(f"Kobo:update_device_database_collections - oncard='{oncard}'") if self.modify_database_check('update_device_database_collections') is False: return @@ -1678,7 +1678,7 @@ class KOBOTOUCH(KOBO): return "'true'" if x else "'false'" def books(self, oncard=None, end_session=True): - debug_print("KoboTouch:books - oncard='%s'"%oncard) + debug_print(f"KoboTouch:books - oncard='{oncard}'") self.debugging_title = self.get_debugging_title() dummy_bl = self.booklist_class(None, None, None) @@ -1699,11 +1699,11 @@ class KOBOTOUCH(KOBO): prefix = self._card_a_prefix if oncard == 'carda' else \ self._card_b_prefix if oncard == 'cardb' \ else self._main_prefix - debug_print("KoboTouch:books - oncard='%s', prefix='%s'"%(oncard, prefix)) + debug_print(f"KoboTouch:books - oncard='{oncard}', prefix='{prefix}'") self.fwversion = self.get_firmware_version() - debug_print('Kobo device: %s' % self.gui_name) + debug_print(f'Kobo device: {self.gui_name}') debug_print('Version of driver:', self.version, 'Has kepubs:', self.has_kepubs) debug_print('Version of firmware:', self.fwversion, 'Has kepubs:', self.has_kepubs) debug_print('Firmware supports cover image tree:', self.fwversion >= self.min_fwversion_images_tree) @@ -1718,7 +1718,7 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:books - driver options=', self) debug_print("KoboTouch:books - prefs['manage_device_metadata']=", prefs['manage_device_metadata']) debugging_title = self.debugging_title - debug_print("KoboTouch:books - set_debugging_title to '%s'" % debugging_title) + debug_print(f"KoboTouch:books - set_debugging_title to '{debugging_title}'") bl.set_debugging_title(debugging_title) debug_print('KoboTouch:books - length bl=%d'%len(bl)) need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE) @@ -1739,7 +1739,7 @@ class KOBOTOUCH(KOBO): show_debug = self.is_debugging_title(title) # show_debug = authors == 'L. Frank Baum' if show_debug: - debug_print("KoboTouch:update_booklist - title='%s'"%title, 'ContentType=%s'%ContentType, 'isdownloaded=', isdownloaded) + 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,)) @@ -1838,19 +1838,19 @@ class KOBOTOUCH(KOBO): try: kobo_metadata.pubdate = datetime.strptime(DateCreated, '%Y-%m-%dT%H:%M:%S.%fZ') except: - debug_print("KoboTouch:update_booklist - Cannot convert date - DateCreated='%s'"%DateCreated) + debug_print(f"KoboTouch:update_booklist - Cannot convert date - DateCreated='{DateCreated}'") idx = bl_cache.get(lpath, None) 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('KoboTouch:update_booklist - lpath=%s'%lpath) + 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) debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves) debug_print('KoboTouch:update_booklist - kobo_collections=', kobo_collections) - debug_print('KoboTouch:update_booklist - series="%s"' % bl[idx].series) + debug_print(f'KoboTouch:update_booklist - series="{bl[idx].series}"') debug_print('KoboTouch:update_booklist - the book=', bl[idx]) debug_print('KoboTouch:update_booklist - the authors=', bl[idx].authors) debug_print('KoboTouch:update_booklist - application_id=', bl[idx].application_id) @@ -1871,7 +1871,7 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:update_booklist - book size=', bl[idx].size) if show_debug: - debug_print("KoboTouch:update_booklist - ContentID='%s'"%ContentID) + debug_print(f"KoboTouch:update_booklist - ContentID='{ContentID}'") bl[idx].contentID = ContentID bl[idx].kobo_metadata = kobo_metadata bl[idx].kobo_series = series @@ -1897,8 +1897,8 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:update_booklist - updated bl[idx].device_collections=', bl[idx].device_collections) debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map, 'changed=', changed) # debug_print('KoboTouch:update_booklist - book=', bl[idx]) - debug_print('KoboTouch:update_booklist - book class=%s'%bl[idx].__class__) - debug_print('KoboTouch:update_booklist - book title=%s'%bl[idx].title) + debug_print(f'KoboTouch:update_booklist - book class={bl[idx].__class__}') + debug_print(f'KoboTouch:update_booklist - book title={bl[idx].title}') else: if show_debug: debug_print('KoboTouch:update_booklist - idx is none') @@ -1911,10 +1911,10 @@ class KOBOTOUCH(KOBO): title = 'FILE MISSING: ' + title book = self.book_class(prefix, lpath, title, authors, MimeType, DateCreated, ContentType, ImageID, size=0) if show_debug: - debug_print('KoboTouch:update_booklist - book file does not exist. ContentID="%s"'%ContentID) + debug_print(f'KoboTouch:update_booklist - book file does not exist. ContentID="{ContentID}"') except Exception as e: - debug_print("KoboTouch:update_booklist - exception creating book: '%s'"%str(e)) + debug_print(f"KoboTouch:update_booklist - exception creating book: '{e!s}'") debug_print(' prefix: ', prefix, 'lpath: ', lpath, 'title: ', title, 'authors: ', authors, 'MimeType: ', MimeType, 'DateCreated: ', DateCreated, 'ContentType: ', ContentType, 'ImageID: ', ImageID) raise @@ -1922,10 +1922,10 @@ class KOBOTOUCH(KOBO): if show_debug: debug_print('KoboTouch:update_booklist - class:', book.__class__) # debug_print(' resolution:', book.__class__.__mro__) - debug_print(" contentid: '%s'"%book.contentID) - debug_print(" title:'%s'"%book.title) + debug_print(f" contentid: '{book.contentID}'") + debug_print(f" title:'{book.title}'") debug_print(' the book:', book) - debug_print(" author_sort:'%s'"%book.author_sort) + debug_print(f" author_sort:'{book.author_sort}'") debug_print(' bookshelves:', bookshelves) debug_print(' kobo_collections:', kobo_collections) @@ -2021,39 +2021,35 @@ class KOBOTOUCH(KOBO): if self.supports_kobo_archive() or self.supports_overdrive(): where_clause = (" WHERE BookID IS NULL " " AND ((Accessibility = -1 AND IsDownloaded in ('true', 1 )) " # Sideloaded books - " OR (Accessibility IN (%(downloaded_accessibility)s) %(expiry)s) " # Purchased books - " %(previews)s %(recommendations)s ) " # Previews or Recommendations - ) % \ - dict( + " OR (Accessibility IN ({downloaded_accessibility}) {expiry}) " # Purchased books + " {previews} {recommendations} ) " # Previews or Recommendations + ).format(**dict( expiry='' if self.show_archived_books else "and IsDownloaded in ('true', 1)", previews=" OR (Accessibility in (6) AND ___UserID <> '')" if self.show_previews else '', recommendations=" OR (Accessibility IN (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else '', downloaded_accessibility='1,2,8,9' if self.supports_overdrive() else '1,2' - ) + )) elif self.supports_series(): where_clause = (" WHERE BookID IS NULL " - " AND ((Accessibility = -1 AND IsDownloaded IN ('true', 1)) or (Accessibility IN (1,2)) %(previews)s %(recommendations)s )" - " AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus is Null) %(expiry)s)" - ) % \ - dict( + " AND ((Accessibility = -1 AND IsDownloaded IN ('true', 1)) or (Accessibility IN (1,2)) {previews} {recommendations} )" + " AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus is Null) {expiry})" + ).format(**dict( expiry=' AND ContentType = 6' if self.show_archived_books else '', previews=" or (Accessibility IN (6) AND ___UserID <> '')" if self.show_previews else '', recommendations=" or (Accessibility in (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else '' - ) + )) elif self.dbversion >= 33: - where_clause = (' WHERE BookID IS NULL %(previews)s %(recommendations)s AND NOT' - ' ((___ExpirationStatus=3 or ___ExpirationStatus IS NULL) %(expiry)s)' - ) % \ - dict( + where_clause = (' WHERE BookID IS NULL {previews} {recommendations} AND NOT' + ' ((___ExpirationStatus=3 or ___ExpirationStatus IS NULL) {expiry})' + ).format(**dict( expiry=' AND ContentType = 6' if self.show_archived_books else '', previews=' AND Accessibility <> 6' if not self.show_previews else '', recommendations=" AND IsDownloaded IN ('true', 1)" if not self.show_recommendations else '' - ) + )) elif self.dbversion >= 16: where_clause = (' WHERE BookID IS NULL ' - 'AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus IS Null) %(expiry)s)' - ) % \ - dict(expiry=' and ContentType = 6' if self.show_archived_books else '') + 'AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus IS Null) {expiry})' + ).format(**dict(expiry=' and ContentType = 6' if self.show_archived_books else '')) else: where_clause = ' WHERE BookID IS NULL' @@ -2094,7 +2090,7 @@ class KOBOTOUCH(KOBO): show_debug = self.is_debugging_title(row['Title']) if show_debug: debug_print('KoboTouch:books - looping on database - row=%d' % i) - debug_print("KoboTouch:books - title='%s'"%row['Title'], 'authors=', row['Attribution']) + 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( 'file:///usr/local/kobo/help/') or row['ContentID'].lower().startswith('/usr/local/kobo/help/'): @@ -2103,7 +2099,7 @@ class KOBOTOUCH(KOBO): externalId = None if row['ExternalId'] and len(row['ExternalId']) == 0 else row['ExternalId'] path = self.path_from_contentid(row['ContentID'], row['ContentType'], row['MimeType'], oncard, externalId) if show_debug: - debug_print("KoboTouch:books - path='%s'"%path, " ContentID='%s'"%row['ContentID'], ' externalId=%s' % externalId) + debug_print(f"KoboTouch:books - path='{path}'", " ContentID='{}'".format(row['ContentID']), f' externalId={externalId}') bookshelves = get_bookshelvesforbook(connection, row['ContentID']) @@ -2142,7 +2138,7 @@ class KOBOTOUCH(KOBO): need_sync = True del bl[idx] else: - debug_print("KoboTouch:books - Book in mtadata.calibre, on file system but not database - bl[idx].title:'%s'"%bl[idx].title) + debug_print(f"KoboTouch:books - Book in mtadata.calibre, on file system but not database - bl[idx].title:'{bl[idx].title}'") # print('count found in cache: %d, count of files in metadata: %d, need_sync: %s' % \ # (len(bl_cache), len(bl), need_sync)) @@ -2159,12 +2155,12 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:books - have done sync_booklists') self.report_progress(1.0, _('Getting list of books on device...')) - debug_print("KoboTouch:books - end - oncard='%s'"%oncard) + debug_print(f"KoboTouch:books - end - oncard='{oncard}'") return bl @classmethod def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID): - debug_print('KoboTouch:book_from_path - title=%s'%title) + debug_print(f'KoboTouch:book_from_path - title={title}') book = super().book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID) # Kobo Audiobooks are directories with files in them. @@ -2222,11 +2218,11 @@ class KOBOTOUCH(KOBO): fpath = path + ending if os.path.exists(fpath): if show_debug: - debug_print('KoboTouch:imagefilename_from_imageID - have cover image fpath=%s' % (fpath)) + debug_print(f'KoboTouch:imagefilename_from_imageID - have cover image fpath={fpath}') return fpath if show_debug: - debug_print('KoboTouch:imagefilename_from_imageID - no cover image found - ImageID=%s' % (ImageID)) + debug_print(f'KoboTouch:imagefilename_from_imageID - no cover image found - ImageID={ImageID}') return None def get_extra_css(self): @@ -2313,7 +2309,7 @@ class KOBOTOUCH(KOBO): cursor.close() except Exception as e: - debug_print('KoboTouch:upload_books - Exception: %s'%str(e)) + debug_print(f'KoboTouch:upload_books - Exception: {e!s}') return result @@ -2419,7 +2415,7 @@ class KOBOTOUCH(KOBO): imageId = super().delete_via_sql(ContentID, ContentType) if self.dbversion >= 53: - debug_print('KoboTouch:delete_via_sql: ContentID="%s"'%ContentID, 'ContentType="%s"'%ContentType) + debug_print(f'KoboTouch:delete_via_sql: ContentID="{ContentID}"', f'ContentType="{ContentType}"') try: with closing(self.device_database_connection()) as connection: debug_print('KoboTouch:delete_via_sql: have database connection') @@ -2457,9 +2453,9 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:delete_via_sql: finished SQL') debug_print('KoboTouch:delete_via_sql: After SQL, no exception') except Exception as e: - debug_print('KoboTouch:delete_via_sql - Database Exception: %s'%str(e)) + debug_print(f'KoboTouch:delete_via_sql - Database Exception: {e!s}') - debug_print('KoboTouch:delete_via_sql: imageId="%s"'%imageId) + debug_print(f'KoboTouch:delete_via_sql: imageId="{imageId}"') if imageId is None: imageId = self.imageid_from_contentid(ContentID) @@ -2469,12 +2465,12 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:delete_images - ImageID=', ImageID) if ImageID is not None: path = self.images_path(book_path, ImageID) - debug_print('KoboTouch:delete_images - path=%s' % path) + debug_print(f'KoboTouch:delete_images - path={path}') for ending in self.cover_file_endings().keys(): fpath = path + ending fpath = self.normalize_path(fpath) - debug_print('KoboTouch:delete_images - fpath=%s' % fpath) + debug_print(f'KoboTouch:delete_images - fpath={fpath}') if os.path.exists(fpath): debug_print('KoboTouch:delete_images - Image File Exists') @@ -2488,8 +2484,8 @@ class KOBOTOUCH(KOBO): def contentid_from_path(self, path, ContentType): show_debug = self.is_debugging_title(path) and True if show_debug: - debug_print("KoboTouch:contentid_from_path - path='%s'"%path, "ContentType='%s'"%ContentType) - debug_print("KoboTouch:contentid_from_path - self._main_prefix='%s'"%self._main_prefix, "self._card_a_prefix='%s'"%self._card_a_prefix) + debug_print(f"KoboTouch:contentid_from_path - path='{path}'", f"ContentType='{ContentType}'") + debug_print(f"KoboTouch:contentid_from_path - self._main_prefix='{self._main_prefix}'", f"self._card_a_prefix='{self._card_a_prefix}'") if ContentType == 6: extension = os.path.splitext(path)[1] if extension == '.kobo': @@ -2504,19 +2500,19 @@ class KOBOTOUCH(KOBO): ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/') if show_debug: - debug_print("KoboTouch:contentid_from_path - 1 ContentID='%s'"%ContentID) + debug_print(f"KoboTouch:contentid_from_path - 1 ContentID='{ContentID}'") 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, "path='%s'"%path) + debug_print("KoboTouch:contentid_from_path ContentType other than 6 - ContentType='%d'"%ContentType, f"path='{path}'") ContentID = path ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/') if self._card_a_prefix is not None: ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/') ContentID = ContentID.replace('\\', '/') if show_debug: - debug_print("KoboTouch:contentid_from_path - end - ContentID='%s'"%ContentID) + debug_print(f"KoboTouch:contentid_from_path - end - ContentID='{ContentID}'") return ContentID def get_content_type_from_path(self, path): @@ -2538,8 +2534,8 @@ class KOBOTOUCH(KOBO): self.plugboard_func = pb_func def update_device_database_collections(self, booklists, collections_attributes, oncard): - debug_print("KoboTouch:update_device_database_collections - oncard='%s'"%oncard) - debug_print("KoboTouch:update_device_database_collections - device='%s'" % self) + debug_print(f"KoboTouch:update_device_database_collections - oncard='{oncard}'") + debug_print(f"KoboTouch:update_device_database_collections - device='{self}'") if self.modify_database_check('update_device_database_collections') is False: return @@ -2573,7 +2569,7 @@ class KOBOTOUCH(KOBO): update_core_metadata = self.update_core_metadata update_purchased_kepubs = self.update_purchased_kepubs debugging_title = self.get_debugging_title() - debug_print("KoboTouch:update_device_database_collections - set_debugging_title to '%s'" % debugging_title) + debug_print(f"KoboTouch:update_device_database_collections - set_debugging_title to '{debugging_title}'") booklists.set_debugging_title(debugging_title) booklists.set_device_managed_collections(self.ignore_collections_names) @@ -2623,11 +2619,11 @@ class KOBOTOUCH(KOBO): # debug_print(' Title:', book.title, 'category: ', category) show_debug = self.is_debugging_title(book.title) if show_debug: - debug_print(' Title="%s"'%book.title, 'category="%s"'%category) + debug_print(f' Title="{book.title}"', f'category="{category}"') # debug_print(book) - debug_print(' class=%s'%book.__class__) - debug_print(' book.contentID="%s"'%book.contentID) - debug_print(' book.application_id="%s"'%book.application_id) + debug_print(f' class={book.__class__}') + debug_print(f' book.contentID="{book.contentID}"') + debug_print(f' book.application_id="{book.application_id}"') if book.application_id is None: continue @@ -2635,13 +2631,13 @@ class KOBOTOUCH(KOBO): category_added = False if book.contentID is None: - debug_print(' Do not know ContentID - Title="%s", Authors="%s", path="%s"'%(book.title, book.author, book.path)) + debug_print(f' Do not know ContentID - Title="{book.title}", Authors="{book.author}", path="{book.path}"') extension = os.path.splitext(book.path)[1] ContentType = self.get_content_type_from_extension(extension) if extension else self.get_content_type_from_path(book.path) book.contentID = self.contentid_from_path(book.path, ContentType) if category in self.ignore_collections_names: - debug_print(' Ignoring collection=%s' % category) + debug_print(f' Ignoring collection={category}') category_added = True elif category in self.bookshelvelist and self.supports_bookshelves: if show_debug: @@ -2652,18 +2648,18 @@ class KOBOTOUCH(KOBO): self.set_bookshelf(connection, book, category) category_added = True elif category in readstatuslist: - debug_print("KoboTouch:update_device_database_collections - about to set_readstatus - category='%s'"%(category, )) + debug_print(f"KoboTouch:update_device_database_collections - about to set_readstatus - category='{category}'") # Manage ReadStatus self.set_readstatus(connection, book.contentID, readstatuslist.get(category)) category_added = True elif category == 'Shortlist' and self.dbversion >= 14: if show_debug: - debug_print(' Have an older version shortlist - %s'%book.title) + debug_print(f' Have an older version shortlist - {book.title}') # Manage FavouritesIndex/Shortlist if not self.supports_bookshelves: if show_debug: - debug_print(' and about to set it - %s'%book.title) + debug_print(f' and about to set it - {book.title}') self.set_favouritesindex(connection, book.contentID) category_added = True elif category in accessibilitylist: @@ -2677,7 +2673,7 @@ class KOBOTOUCH(KOBO): else: if show_debug: debug_print(' category not added to book.device_collections', book.device_collections) - debug_print("KoboTouch:update_device_database_collections - end for category='%s'"%category) + debug_print(f"KoboTouch:update_device_database_collections - end for category='{category}'") elif have_bookshelf_attributes: # No collections but have set the shelf option # Since no collections exist the ReadStatus needs to be reset to 0 (Unread) @@ -2702,11 +2698,10 @@ class KOBOTOUCH(KOBO): books_in_library += 1 show_debug = self.is_debugging_title(book.title) if show_debug: - debug_print('KoboTouch:update_device_database_collections - book.title=%s' % book.title) + debug_print(f'KoboTouch:update_device_database_collections - book.title={book.title}') debug_print( - 'KoboTouch:update_device_database_collections - contentId=%s,' - 'update_core_metadata=%s,update_purchased_kepubs=%s, book.is_sideloaded=%s' % ( - book.contentID, update_core_metadata, update_purchased_kepubs, book.is_sideloaded)) + f'KoboTouch:update_device_database_collections - contentId={book.contentID},' + f'update_core_metadata={update_core_metadata},update_purchased_kepubs={update_purchased_kepubs}, book.is_sideloaded={book.is_sideloaded}') if update_core_metadata and (update_purchased_kepubs or book.is_sideloaded): if show_debug: debug_print('KoboTouch:update_device_database_collections - calling set_core_metadata') @@ -2717,7 +2712,7 @@ class KOBOTOUCH(KOBO): self.set_core_metadata(connection, book, series_only=True) if self.manage_collections and have_bookshelf_attributes: if show_debug: - debug_print('KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s' % book.title) + debug_print(f'KoboTouch:update_device_database_collections - about to remove a book from shelves book.title={book.title}') self.remove_book_from_device_bookshelves(connection, book) book.device_collections.extend(book.kobo_collections) if not prefs['manage_device_metadata'] == 'manual' and delete_empty_collections: @@ -2749,8 +2744,8 @@ class KOBOTOUCH(KOBO): :param filepath: The full path to the ebook file ''' - debug_print("KoboTouch:upload_cover - path='%s' filename='%s' "%(path, filename)) - debug_print(" filepath='%s' "%(filepath)) + debug_print(f"KoboTouch:upload_cover - path='{path}' filename='{filename}' ") + debug_print(f" filepath='{filepath}' ") if not self.upload_covers: # Building thumbnails disabled @@ -2769,7 +2764,7 @@ class KOBOTOUCH(KOBO): self.keep_cover_aspect, self.letterbox_fs_covers, self.png_covers, letterbox_color=self.letterbox_fs_covers_color) except Exception as e: - debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, str(e))) + debug_print(f'KoboTouch: FAILED to upload cover={filepath} Exception={e!s}') def imageid_from_contentid(self, ContentID): ImageID = ContentID.replace('/', '_') @@ -2793,7 +2788,7 @@ class KOBOTOUCH(KOBO): hash1 = qhash(imageId) dir1 = hash1 & (0xff * 1) dir2 = (hash1 & (0xff00 * 1)) >> 8 - path = os.path.join(path, '%s' % dir1, '%s' % dir2) + path = os.path.join(path, f'{dir1}', f'{dir2}') if imageId: path = os.path.join(path, imageId) @@ -2864,15 +2859,15 @@ class KOBOTOUCH(KOBO): ): from calibre.utils.img import optimize_png from calibre.utils.imghdr import identify - debug_print("KoboTouch:_upload_cover - filename='%s' upload_grayscale='%s' dithered_covers='%s' "%(filename, upload_grayscale, dithered_covers)) + debug_print(f"KoboTouch:_upload_cover - filename='{filename}' upload_grayscale='{upload_grayscale}' dithered_covers='{dithered_covers}' ") if not metadata.cover: return show_debug = self.is_debugging_title(filename) if show_debug: - debug_print("KoboTouch:_upload_cover - path='%s'"%path, "filename='%s'"%filename) - debug_print(" filepath='%s'"%filepath) + debug_print(f"KoboTouch:_upload_cover - path='{path}'", f"filename='{filename}'") + debug_print(f" filepath='{filepath}'") cover = self.normalize_path(metadata.cover.replace('/', os.sep)) if not os.path.exists(cover): @@ -2895,7 +2890,7 @@ class KOBOTOUCH(KOBO): ImageID = result[0] except StopIteration: ImageID = self.imageid_from_contentid(ContentID) - debug_print("KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='%s'" % ImageID) + debug_print(f"KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='{ImageID}'") cursor.close() @@ -2907,7 +2902,7 @@ class KOBOTOUCH(KOBO): image_dir = os.path.dirname(os.path.abspath(path)) if not os.path.exists(image_dir): - debug_print("KoboTouch:_upload_cover - Image folder does not exist. Creating path='%s'" % (image_dir)) + debug_print(f"KoboTouch:_upload_cover - Image folder does not exist. Creating path='{image_dir}'") os.makedirs(image_dir) with open(cover, 'rb') as f: @@ -2924,7 +2919,7 @@ class KOBOTOUCH(KOBO): if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion: if show_debug: - debug_print("KoboTouch:_upload_cover - creating cover for ending='%s'"%ending) # , "library_cover_size'%s'"%library_cover_size) + debug_print(f"KoboTouch:_upload_cover - creating cover for ending='{ending}'") # , "library_cover_size'%s'"%library_cover_size) fpath = path + ending fpath = self.normalize_path(fpath.replace('/', os.sep)) @@ -2943,9 +2938,8 @@ class KOBOTOUCH(KOBO): resize_to, expand_to = self._calculate_kobo_cover_size(library_cover_size, kobo_size, not is_full_size, keep_cover_aspect, letterbox) if show_debug: debug_print( - 'KoboTouch:_calculate_kobo_cover_size - expand_to=%s' - ' (vs. kobo_size=%s) & resize_to=%s, keep_cover_aspect=%s & letterbox_fs_covers=%s, png_covers=%s' % ( - expand_to, kobo_size, resize_to, keep_cover_aspect, letterbox_fs_covers, png_covers)) + f'KoboTouch:_calculate_kobo_cover_size - expand_to={expand_to}' + f' (vs. kobo_size={kobo_size}) & resize_to={resize_to}, keep_cover_aspect={keep_cover_aspect} & letterbox_fs_covers={letterbox_fs_covers}, png_covers={png_covers}') # NOTE: To speed things up, we enforce a lower # compression level for png_covers, as the final @@ -2983,7 +2977,7 @@ class KOBOTOUCH(KOBO): fsync(f) except Exception as e: err = str(e) - debug_print('KoboTouch:_upload_cover - Exception string: %s'%err) + debug_print(f'KoboTouch:_upload_cover - Exception string: {err}') raise def remove_book_from_device_bookshelves(self, connection, book): @@ -2993,8 +2987,8 @@ class KOBOTOUCH(KOBO): remove_shelf_list = remove_shelf_list - set(self.ignore_collections_names) if show_debug: - debug_print('KoboTouch:remove_book_from_device_bookshelves - book.application_id="%s"'%book.application_id) - debug_print('KoboTouch:remove_book_from_device_bookshelves - book.contentID="%s"'%book.contentID) + debug_print(f'KoboTouch:remove_book_from_device_bookshelves - book.application_id="{book.application_id}"') + debug_print(f'KoboTouch:remove_book_from_device_bookshelves - book.contentID="{book.contentID}"') debug_print('KoboTouch:remove_book_from_device_bookshelves - book.device_collections=', book.device_collections) debug_print('KoboTouch:remove_book_from_device_bookshelves - book.current_shelves=', book.current_shelves) debug_print('KoboTouch:remove_book_from_device_bookshelves - remove_shelf_list=', remove_shelf_list) @@ -3009,12 +3003,12 @@ class KOBOTOUCH(KOBO): if book.device_collections: placeholder = '?' placeholders = ','.join(placeholder for unused in book.device_collections) - query += ' and ShelfName not in (%s)' % placeholders + query += f' and ShelfName not in ({placeholders})' values.extend(book.device_collections) if show_debug: - debug_print('KoboTouch:remove_book_from_device_bookshelves query="%s"'%query) - debug_print('KoboTouch:remove_book_from_device_bookshelves values="%s"'%values) + debug_print(f'KoboTouch:remove_book_from_device_bookshelves query="{query}"') + debug_print(f'KoboTouch:remove_book_from_device_bookshelves values="{values}"') cursor = connection.cursor() cursor.execute(query, values) @@ -3023,7 +3017,7 @@ class KOBOTOUCH(KOBO): def set_filesize_in_device_database(self, connection, contentID, fpath): show_debug = self.is_debugging_title(fpath) if show_debug: - debug_print('KoboTouch:set_filesize_in_device_database contentID="%s"'%contentID) + debug_print(f'KoboTouch:set_filesize_in_device_database contentID="{contentID}"') test_query = ('SELECT ___FileSize ' 'FROM content ' @@ -3136,8 +3130,8 @@ class KOBOTOUCH(KOBO): def set_bookshelf(self, connection, book, shelfName): show_debug = self.is_debugging_title(book.title) if show_debug: - debug_print('KoboTouch:set_bookshelf book.ContentID="%s"'%book.contentID) - debug_print('KoboTouch:set_bookshelf book.current_shelves="%s"'%book.current_shelves) + debug_print(f'KoboTouch:set_bookshelf book.ContentID="{book.contentID}"') + debug_print(f'KoboTouch:set_bookshelf book.current_shelves="{book.current_shelves}"') if shelfName in book.current_shelves: if show_debug: @@ -3175,7 +3169,7 @@ class KOBOTOUCH(KOBO): def check_for_bookshelf(self, connection, bookshelf_name): show_debug = self.is_debugging_title(bookshelf_name) if show_debug: - debug_print('KoboTouch:check_for_bookshelf bookshelf_name="%s"'%bookshelf_name) + debug_print(f'KoboTouch:check_for_bookshelf bookshelf_name="{bookshelf_name}"') test_query = 'SELECT InternalName, Name, _IsDeleted FROM Shelf WHERE Name = ?' test_values = (bookshelf_name, ) addquery = 'INSERT INTO "main"."Shelf"' @@ -3220,7 +3214,7 @@ class KOBOTOUCH(KOBO): if result is None: if show_debug: - debug_print(' Did not find a record - adding shelf "%s"' % bookshelf_name) + debug_print(f' Did not find a record - adding shelf "{bookshelf_name}"') cursor.execute(addquery, add_values) elif self.is_true_value(result['_IsDeleted']): debug_print("KoboTouch:check_for_bookshelf - Shelf '{}' is deleted - undeleting. result['_IsDeleted']='{}'".format( @@ -3253,7 +3247,7 @@ class KOBOTOUCH(KOBO): if bookshelves: placeholder = '?' placeholders = ','.join(placeholder for unused in bookshelves) - query += ' and ShelfName in (%s)' % placeholders + query += f' and ShelfName in ({placeholders})' values.append(bookshelves) debug_print('KoboTouch:remove_from_bookshelf query=', query) debug_print('KoboTouch:remove_from_bookshelf values=', values) @@ -3267,8 +3261,8 @@ class KOBOTOUCH(KOBO): def set_series(self, connection, book): show_debug = self.is_debugging_title(book.title) if show_debug: - debug_print('KoboTouch:set_series book.kobo_series="%s"'%book.kobo_series) - debug_print('KoboTouch:set_series book.series="%s"'%book.series) + debug_print(f'KoboTouch:set_series book.kobo_series="{book.kobo_series}"') + debug_print(f'KoboTouch:set_series book.series="{book.series}"') debug_print('KoboTouch:set_series book.series_index=', book.series_index) if book.series == book.kobo_series: @@ -3289,7 +3283,7 @@ class KOBOTOUCH(KOBO): elif book.series_index is None: # This should never happen, but... update_values = (book.series, None, book.contentID, ) else: - update_values = (book.series, '%g'%book.series_index, book.contentID, ) + update_values = (book.series, f'{book.series_index:g}', book.contentID, ) cursor = connection.cursor() try: @@ -3320,7 +3314,7 @@ class KOBOTOUCH(KOBO): else: new_value = new_value if len(new_value.strip()) else None if new_value is not None and new_value.startswith('PLUGBOARD TEMPLATE ERROR'): - debug_print("KoboTouch:generate_update_from_template template error - template='%s'" % template) + debug_print(f"KoboTouch:generate_update_from_template template error - template='{template}'") debug_print('KoboTouch:generate_update_from_template - new_value=', new_value) # debug_print( @@ -3366,7 +3360,7 @@ class KOBOTOUCH(KOBO): if newmi.series is not None: new_series = newmi.series try: - new_series_number = '%g' % newmi.series_index + new_series_number = f'{newmi.series_index:g}' except: new_series_number = None else: @@ -3463,7 +3457,7 @@ class KOBOTOUCH(KOBO): else: new_subtitle = book.subtitle if len(book.subtitle.strip()) else None if new_subtitle is not None and new_subtitle.startswith('PLUGBOARD TEMPLATE ERROR'): - debug_print("KoboTouch:set_core_metadata subtitle template error - self.subtitle_template='%s'" % self.subtitle_template) + debug_print(f"KoboTouch:set_core_metadata subtitle template error - self.subtitle_template='{self.subtitle_template}'") debug_print('KoboTouch:set_core_metadata - new_subtitle=', new_subtitle) if (new_subtitle is not None and (book.kobo_subtitle is None or book.subtitle != book.kobo_subtitle)) or \ @@ -3509,9 +3503,9 @@ class KOBOTOUCH(KOBO): update_query += ', '.join([col_name + ' = ?' for col_name in set_clause]) changes_found = True if show_debug: - debug_print('KoboTouch:set_core_metadata set_clause="%s"' % set_clause) - debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_values) - debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_query) + debug_print(f'KoboTouch:set_core_metadata set_clause="{set_clause}"') + debug_print(f'KoboTouch:set_core_metadata update_values="{update_values}"') + debug_print(f'KoboTouch:set_core_metadata update_values="{update_query}"') if changes_found: update_query += ' WHERE ContentID = ? AND BookID IS NULL' update_values.append(book.contentID) @@ -4087,9 +4081,9 @@ class KOBOTOUCH(KOBO): ' Kobo forum at MobileRead. This is at %s.' ) % 'https://www.mobileread.com/forums/forumdisplay.php?f=223' + '\n' + ( - '\nDevice database version: %s.' - '\nDevice firmware version: %s' - ) % (self.dbversion, self.display_fwversion), + f'\nDevice database version: {self.dbversion}.' + f'\nDevice firmware version: {self.display_fwversion}' + ), UserFeedback.WARN ) @@ -4206,7 +4200,7 @@ class KOBOTOUCH(KOBO): try: is_debugging = (len(self.debugging_title) > 0 and title.lower().find(self.debugging_title.lower()) >= 0) or len(title) == 0 except: - debug_print(("KoboTouch::is_debugging_title - Exception checking debugging title for title '{}'.").format(title)) + debug_print(f"KoboTouch::is_debugging_title - Exception checking debugging title for title '{title}'.") is_debugging = False return is_debugging diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index d02f89a69f..536b9e6bea 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -98,7 +98,7 @@ class PDNOVEL(USBMS): def upload_cover(self, path, filename, metadata, filepath): coverdata = getattr(metadata, 'thumbnail', None) if coverdata and coverdata[2]: - with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: + with open(f'{os.path.join(path, filename)}.jpg', 'wb') as coverfile: coverfile.write(coverdata[2]) fsync(coverfile) diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index 21852bd911..d0a48f1e6a 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -35,7 +35,7 @@ DEFAULT_THUMBNAIL_HEIGHT = 320 class MTPInvalidSendPathError(PathError): def __init__(self, folder): - PathError.__init__(self, 'Trying to send to ignored folder: %s'%folder) + PathError.__init__(self, f'Trying to send to ignored folder: {folder}') self.folder = folder @@ -405,7 +405,7 @@ class MTP_DEVICE(BASE): except Exception as e: ans.append((path, e, traceback.format_exc())) continue - base = os.path.join(tdir, '%s'%f.object_id) + base = os.path.join(tdir, f'{f.object_id}') os.mkdir(base) name = f.name if iswindows: @@ -628,8 +628,7 @@ class MTP_DEVICE(BASE): try: self.recursive_delete(parent) except: - prints('Failed to delete parent: %s, ignoring'%( - '/'.join(parent.full_path))) + prints('Failed to delete parent: {}, ignoring'.format('/'.join(parent.full_path))) def delete_books(self, paths, end_session=True): self.report_progress(0, _('Deleting books from device...')) @@ -673,7 +672,7 @@ class MTP_DEVICE(BASE): If that is not found looks for a device default and if that is not found uses the global default.''' dd = self.current_device_defaults if self.is_mtp_device_connected else {} - dev_settings = self.prefs.get('device-%s'%self.current_serial_num, {}) + dev_settings = self.prefs.get(f'device-{self.current_serial_num}', {}) default_value = dd.get(key, self.prefs[key]) return dev_settings.get(key, default_value) diff --git a/src/calibre/devices/mtp/filesystem_cache.py b/src/calibre/devices/mtp/filesystem_cache.py index 674abeebdc..049712f8a3 100644 --- a/src/calibre/devices/mtp/filesystem_cache.py +++ b/src/calibre/devices/mtp/filesystem_cache.py @@ -69,8 +69,7 @@ class FileOrFolder: self.last_modified = as_utc(self.last_modified) if self.storage_id not in fs_cache.all_storage_ids: - raise ValueError('Storage id %s not valid for %s, valid values: %s'%(self.storage_id, - entry, fs_cache.all_storage_ids)) + raise ValueError(f'Storage id {self.storage_id} not valid for {entry}, valid values: {fs_cache.all_storage_ids}') self.is_hidden = entry.get('is_hidden', False) self.is_system = entry.get('is_system', False) @@ -92,7 +91,7 @@ class FileOrFolder: self.deleted = False if self.is_storage: - self.storage_prefix = 'mtp:::%s:::'%self.persistent_id + self.storage_prefix = f'mtp:::{self.persistent_id}:::' # Ignore non ebook files and AppleDouble files self.is_ebook = (not self.is_folder and not self.is_storage and @@ -107,11 +106,10 @@ class FileOrFolder: path = str(self.full_path) except Exception: path = '' - datum = 'size=%s'%(self.size) + datum = f'size={self.size}' if self.is_folder or self.is_storage: datum = 'children=%s'%(len(self.files) + len(self.folders)) - return '%s(id=%s, storage_id=%s, %s, path=%s, modified=%s)'%(name, self.object_id, - self.storage_id, datum, path, self.last_mod_string) + return f'{name}(id={self.object_id}, storage_id={self.storage_id}, {datum}, path={path}, modified={self.last_mod_string})' __str__ = __repr__ __unicode__ = __repr__ @@ -171,10 +169,10 @@ class FileOrFolder: def dump(self, prefix='', out=sys.stdout): c = '+' if self.is_folder else '-' - data = ('%s children'%(sum(map(len, (self.files, self.folders)))) + data = (f'{sum(map(len, (self.files, self.folders)))} children' if self.is_folder else human_readable(self.size)) - data += ' modified=%s'%self.last_mod_string - line = '%s%s %s [id:%s %s]'%(prefix, c, self.name, self.object_id, data) + data += f' modified={self.last_mod_string}' + line = f'{prefix}{c} {self.name} [id:{self.object_id} {data}]' prints(line, file=out) for c in (self.folders, self.files): for e in sorted(c, key=lambda x: sort_key(x.name)): @@ -290,14 +288,14 @@ class FilesystemCache: def resolve_mtp_id_path(self, path): if not path.startswith('mtp:::'): - raise ValueError('%s is not a valid MTP path'%path) + raise ValueError(f'{path} is not a valid MTP path') parts = path.split(':::', 2) if len(parts) < 3: - raise ValueError('%s is not a valid MTP path'%path) + raise ValueError(f'{path} is not a valid MTP path') try: object_id = json.loads(parts[1]) except Exception: - raise ValueError('%s is not a valid MTP path'%path) + raise ValueError(f'{path} is not a valid MTP path') id_map = {} path = parts[2] storage_name = path.partition('/')[0] @@ -308,4 +306,4 @@ class FilesystemCache: try: return id_map[object_id] except KeyError: - raise ValueError('No object found with MTP path: %s'%path) + raise ValueError(f'No object found with MTP path: {path}') diff --git a/src/calibre/devices/mtp/test.py b/src/calibre/devices/mtp/test.py index 4fe57e6bd7..0dcfab4a23 100644 --- a/src/calibre/devices/mtp/test.py +++ b/src/calibre/devices/mtp/test.py @@ -182,7 +182,7 @@ class TestDeviceInteraction(unittest.TestCase): return end_mem - start_mem def check_memory(self, once, many, msg, factor=2): - msg += ' for once: %g for many: %g'%(once, many) + msg += f' for once: {once:g} for many: {many:g}' if once > 0: self.assertTrue(many <= once*factor, msg=msg) else: diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 33ef45461c..5bfc3629ca 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -228,8 +228,7 @@ class MTP_DEVICE(MTPDeviceBase): self.dev = self.create_device(connected_device) except Exception as e: self.blacklisted_devices.add(connected_device) - raise OpenFailed('Failed to open %s: Error: %s'%( - connected_device, as_unicode(e))) + raise OpenFailed(f'Failed to open {connected_device}: Error: {as_unicode(e)}') try: storage = sorted_storage(self.dev.storage_info) @@ -259,13 +258,13 @@ class MTP_DEVICE(MTPDeviceBase): storage = [x for x in storage if x.get('rw', False)] if not storage: self.blacklisted_devices.add(connected_device) - raise OpenFailed('No storage found for device %s'%(connected_device,)) + raise OpenFailed(f'No storage found for device {connected_device}') snum = self.dev.serial_number if snum in self.prefs.get('blacklist', []): self.blacklisted_devices.add(connected_device) self.dev = None raise BlacklistedDevice( - 'The %s device has been blacklisted by the user'%(connected_device,)) + f'The {connected_device} device has been blacklisted by the user') self._main_id = storage[0]['id'] self._carda_id = self._cardb_id = None if len(storage) > 1: @@ -281,11 +280,11 @@ class MTP_DEVICE(MTPDeviceBase): @synchronous def device_debug_info(self): ans = self.get_gui_name() - ans += '\nSerial number: %s'%self.current_serial_num - ans += '\nManufacturer: %s'%self.dev.manufacturer_name - ans += '\nModel: %s'%self.dev.model_name - ans += '\nids: %s'%(self.dev.ids,) - ans += '\nDevice version: %s'%self.dev.device_version + ans += f'\nSerial number: {self.current_serial_num}' + ans += f'\nManufacturer: {self.dev.manufacturer_name}' + ans += f'\nModel: {self.dev.model_name}' + ans += f'\nids: {self.dev.ids}' + ans += f'\nDevice version: {self.dev.device_version}' ans += '\nStorage:\n' storage = sorted_storage(self.dev.storage_info) ans += pprint.pformat(storage) @@ -306,7 +305,7 @@ class MTP_DEVICE(MTPDeviceBase): path = tuple(reversed(path)) ok = not self.is_folder_ignored(self._currently_getting_sid, path) if not ok: - debug('Ignored object: %s' % '/'.join(path)) + debug('Ignored object: {}'.format('/'.join(path))) return ok @property @@ -335,14 +334,10 @@ class MTP_DEVICE(MTPDeviceBase): all_items.extend(items), all_errs.extend(errs) if not all_items and all_errs: raise DeviceError( - 'Failed to read filesystem from %s with errors: %s' - %(self.current_friendly_name, - self.format_errorstack(all_errs))) + f'Failed to read filesystem from {self.current_friendly_name} with errors: {self.format_errorstack(all_errs)}') if all_errs: prints('There were some errors while getting the ' - ' filesystem from %s: %s'%( - self.current_friendly_name, - self.format_errorstack(all_errs))) + f' filesystem from {self.current_friendly_name}: {self.format_errorstack(all_errs)}') self._filesystem_cache = FilesystemCache(storage, all_items) debug('Filesystem metadata loaded in %g seconds (%d objects)'%( time.time()-st, len(self._filesystem_cache))) @@ -377,7 +372,7 @@ class MTP_DEVICE(MTPDeviceBase): @synchronous def create_folder(self, parent, name): if not parent.is_folder: - raise ValueError('%s is not a folder'%(parent.full_path,)) + raise ValueError(f'{parent.full_path} is not a folder') e = parent.folder_named(name) if e is not None: return e @@ -387,21 +382,18 @@ class MTP_DEVICE(MTPDeviceBase): ans, errs = self.dev.create_folder(sid, pid, name) if ans is None: raise DeviceError( - 'Failed to create folder named %s in %s with error: %s'% - (name, parent.full_path, self.format_errorstack(errs))) + f'Failed to create folder named {name} in {parent.full_path} with error: {self.format_errorstack(errs)}') return parent.add_child(ans) @synchronous def put_file(self, parent, name, stream, size, callback=None, replace=True): e = parent.folder_named(name) if e is not None: - raise ValueError('Cannot upload file, %s already has a folder named: %s'%( - parent.full_path, e.name)) + raise ValueError(f'Cannot upload file, {parent.full_path} already has a folder named: {e.name}') e = parent.file_named(name) if e is not None: if not replace: - raise ValueError('Cannot upload file %s, it already exists'%( - e.full_path,)) + raise ValueError(f'Cannot upload file {e.full_path}, it already exists') self.delete_file_or_folder(e) sid, pid = parent.storage_id, parent.object_id if pid == sid: @@ -409,21 +401,19 @@ class MTP_DEVICE(MTPDeviceBase): ans, errs = self.dev.put_file(sid, pid, name, stream, size, callback) if ans is None: - raise DeviceError('Failed to upload file named: %s to %s: %s' - %(name, parent.full_path, self.format_errorstack(errs))) + raise DeviceError(f'Failed to upload file named: {name} to {parent.full_path}: {self.format_errorstack(errs)}') return parent.add_child(ans) @synchronous def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: - raise ValueError('%s if a folder'%(f.full_path,)) + raise ValueError(f'{f.full_path} if a folder') set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') ok, errs = self.dev.get_file(f.object_id, stream, callback) if not ok: - raise DeviceError('Failed to get file: %s with errors: %s'%( - f.full_path, self.format_errorstack(errs))) + raise DeviceError(f'Failed to get file: {f.full_path} with errors: {self.format_errorstack(errs)}') stream.seek(0) if set_name: stream.name = f.name @@ -476,18 +466,14 @@ class MTP_DEVICE(MTPDeviceBase): if obj.deleted: return if not obj.can_delete: - raise ValueError('Cannot delete %s as deletion not allowed'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as deletion not allowed') if obj.is_system: - raise ValueError('Cannot delete %s as it is a system object'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as it is a system object') if obj.files or obj.folders: - raise ValueError('Cannot delete %s as it is not empty'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as it is not empty') parent = obj.parent ok, errs = self.dev.delete_object(obj.object_id) if not ok: - raise DeviceError('Failed to delete %s with error: %s'% - (obj.full_path, self.format_errorstack(errs))) + raise DeviceError(f'Failed to delete {obj.full_path} with error: {self.format_errorstack(errs)}') parent.remove_child(obj) return parent diff --git a/src/calibre/devices/mtp/unix/sysfs.py b/src/calibre/devices/mtp/unix/sysfs.py index 12f3c1299c..4c87194076 100644 --- a/src/calibre/devices/mtp/unix/sysfs.py +++ b/src/calibre/devices/mtp/unix/sysfs.py @@ -34,7 +34,7 @@ class MTPDetect: except OSError: pass - ipath = os.path.join(self.base, '{0}-*/{0}-*/interface'.format(dev.busnum)) + ipath = os.path.join(self.base, f'{dev.busnum}-*/{dev.busnum}-*/interface') for x in glob.glob(ipath): raw = read(x) if not raw or raw.strip() != b'MTP': @@ -44,8 +44,8 @@ class MTPDetect: try: if raw and int(raw) == dev.devnum: if debug is not None: - debug('Unknown device {} claims to be an MTP device' - .format(dev)) + debug(f'Unknown device {dev} claims to be an MTP device' + ) return True except (ValueError, TypeError): continue diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index 5203a30495..5b9eae38e5 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -258,7 +258,7 @@ class MTP_DEVICE(MTPDeviceBase): path = tuple(reversed(path)) ok = not self.is_folder_ignored(self._currently_getting_sid, path) if not ok: - debug('Ignored object: %s' % '/'.join(path)) + debug('Ignored object: {}'.format('/'.join(path))) return ok @property @@ -330,19 +330,18 @@ class MTP_DEVICE(MTPDeviceBase): self.dev = self.wpd.Device(connected_device) except self.wpd.WPDError as e: self.blacklisted_devices.add(connected_device) - raise OpenFailed('Failed to open %s with error: %s'%( - connected_device, as_unicode(e))) + raise OpenFailed(f'Failed to open {connected_device} with error: {as_unicode(e)}') devdata = self.dev.data storage = [s for s in devdata.get('storage', []) if s.get('rw', False)] if not storage: self.blacklisted_devices.add(connected_device) - raise OpenFailed('No storage found for device %s'%(connected_device,)) + raise OpenFailed(f'No storage found for device {connected_device}') snum = devdata.get('serial_number', None) if snum in self.prefs.get('blacklist', []): self.blacklisted_devices.add(connected_device) self.dev = None raise BlacklistedDevice( - 'The %s device has been blacklisted by the user'%(connected_device,)) + f'The {connected_device} device has been blacklisted by the user') storage = sorted_storage(storage) @@ -435,7 +434,7 @@ class MTP_DEVICE(MTPDeviceBase): @same_thread def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: - raise ValueError('%s if a folder'%(f.full_path,)) + raise ValueError(f'{f.full_path} if a folder') set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') @@ -446,8 +445,7 @@ class MTP_DEVICE(MTPDeviceBase): time.sleep(2) self.dev.get_file(f.object_id, stream, callback) except Exception as e: - raise DeviceError('Failed to fetch the file %s with error: %s'% - (f.full_path, as_unicode(e))) + raise DeviceError(f'Failed to fetch the file {f.full_path} with error: {as_unicode(e)}') stream.seek(0) if set_name: stream.name = f.name @@ -456,7 +454,7 @@ class MTP_DEVICE(MTPDeviceBase): @same_thread def create_folder(self, parent, name): if not parent.is_folder: - raise ValueError('%s is not a folder'%(parent.full_path,)) + raise ValueError(f'{parent.full_path} is not a folder') e = parent.folder_named(name) if e is not None: return e @@ -472,14 +470,11 @@ class MTP_DEVICE(MTPDeviceBase): if obj.deleted: return if not obj.can_delete: - raise ValueError('Cannot delete %s as deletion not allowed'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as deletion not allowed') if obj.is_system: - raise ValueError('Cannot delete %s as it is a system object'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as it is a system object') if obj.files or obj.folders: - raise ValueError('Cannot delete %s as it is not empty'% - (obj.full_path,)) + raise ValueError(f'Cannot delete {obj.full_path} as it is not empty') parent = obj.parent self.dev.delete_object(obj.object_id) parent.remove_child(obj) @@ -489,13 +484,11 @@ class MTP_DEVICE(MTPDeviceBase): def put_file(self, parent, name, stream, size, callback=None, replace=True): e = parent.folder_named(name) if e is not None: - raise ValueError('Cannot upload file, %s already has a folder named: %s'%( - parent.full_path, e.name)) + raise ValueError(f'Cannot upload file, {parent.full_path} already has a folder named: {e.name}') e = parent.file_named(name) if e is not None: if not replace: - raise ValueError('Cannot upload file %s, it already exists'%( - e.full_path,)) + raise ValueError(f'Cannot upload file {e.full_path}, it already exists') self.delete_file_or_folder(e) sid, pid = parent.storage_id, parent.object_id ans = self.dev.put_file(pid, name, stream, size, callback) diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index 6e77efce8d..1ffd5202d1 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -70,7 +70,7 @@ class NOOK(USBMS): cover.save(data, 'JPEG') coverdata = data.getvalue() - with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: + with open(f'{os.path.join(path, filename)}.jpg', 'wb') as coverfile: coverfile.write(coverdata) fsync(coverfile) diff --git a/src/calibre/devices/paladin/driver.py b/src/calibre/devices/paladin/driver.py index 750aef630e..8d799484d5 100644 --- a/src/calibre/devices/paladin/driver.py +++ b/src/calibre/devices/paladin/driver.py @@ -214,11 +214,11 @@ class PALADIN(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The Paladin database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) def get_database_min_id(self, source_id): @@ -261,11 +261,11 @@ class PALADIN(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The Paladin database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) # Get the books themselves, but keep track of any that are less than the minimum. @@ -398,11 +398,11 @@ class PALADIN(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The Paladin database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) db_collections = {} diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index f24cd9ce10..04bfe484bc 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -170,7 +170,7 @@ class PRS505(USBMS): def filename_callback(self, fname, mi): if getattr(mi, 'application_id', None) is not None: base = fname.rpartition('.')[0] - suffix = '_%s'%mi.application_id + suffix = f'_{mi.application_id}' if not base.endswith(suffix): fname = base + suffix + '.' + fname.rpartition('.')[-1] return fname @@ -183,7 +183,7 @@ class PRS505(USBMS): ('card_a', CACHE_XML, CACHE_EXT, 1), ('card_b', CACHE_XML, CACHE_EXT, 2) ]: - prefix = getattr(self, '_%s_prefix'%prefix) + prefix = getattr(self, f'_{prefix}_prefix') if prefix is not None and os.path.exists(prefix): paths[source_id] = os.path.join(prefix, *(path.split('/'))) ext_paths[source_id] = os.path.join(prefix, *(ext_path.split('/'))) @@ -298,4 +298,4 @@ class PRS505(USBMS): cpath = os.path.join(thumbnail_dir, 'main_thumbnail.jpg') with open(cpath, 'wb') as f: f.write(metadata.thumbnail[-1]) - debug_print('Cover uploaded to: %r'%cpath) + debug_print(f'Cover uploaded to: {cpath!r}') diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index bced104cf4..0e6ed73c3a 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -103,8 +103,8 @@ class XMLCache: for source_id, path in paths.items(): if source_id == 0: if not os.path.exists(path): - raise DeviceError(('The SONY XML cache %r does not exist. Try' - ' disconnecting and reconnecting your reader.')%repr(path)) + raise DeviceError(f'The SONY XML cache {repr(path)!r} does not exist. Try' + ' disconnecting and reconnecting your reader.') with open(path, 'rb') as f: raw = f.read() else: @@ -117,8 +117,8 @@ class XMLCache: xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0] ) if self.roots[source_id] is None: - raise Exception(('The SONY database at %r is corrupted. Try ' - ' disconnecting and reconnecting your reader.')%path) + raise Exception(f'The SONY database at {path!r} is corrupted. Try ' + ' disconnecting and reconnecting your reader.') self.ext_paths, self.ext_roots = {}, {} for source_id, path in ext_paths.items(): @@ -265,7 +265,7 @@ class XMLCache: if title in self._playlist_to_playlist_id_map[bl_idx]: return self._playlist_to_playlist_id_map[bl_idx][title] debug_print('Creating playlist:', title) - ans = root.makeelement('{%s}playlist'%self.namespaces[bl_idx], + ans = root.makeelement(f'{{{self.namespaces[bl_idx]}}}playlist', nsmap=root.nsmap, attrib={ 'uuid' : uuid(), 'title': title, @@ -303,11 +303,11 @@ class XMLCache: if id_ in idmap: item.set('id', idmap[id_]) if DEBUG: - debug_print('Remapping id %s to %s'%(id_, idmap[id_])) + debug_print(f'Remapping id {id_} to {idmap[id_]}') def ensure_media_xml_base_ids(root): for num, tag in enumerate(('library', 'watchSpecial')): - for x in root.xpath('//*[local-name()="%s"]'%tag): + for x in root.xpath(f'//*[local-name()="{tag}"]'): x.set('id', str(num)) def rebase_ids(root, base, sourceid, pl_sourceid): @@ -538,7 +538,7 @@ class XMLCache: # add the ids that get_collections didn't know about. for id_ in ids + extra_ids: item = playlist.makeelement( - '{%s}item'%self.namespaces[bl_index], + f'{{{self.namespaces[bl_index]}}}item', nsmap=playlist.nsmap, attrib={'id':id_}) playlist.append(item) @@ -569,14 +569,14 @@ class XMLCache: attrib = { 'page':'0', 'part':'0','pageOffset':'0','scale':'0', 'id':str(id_), 'sourceid':'1', 'path':lpath} - ans = root.makeelement('{%s}text'%namespace, attrib=attrib, nsmap=root.nsmap) + ans = root.makeelement(f'{{{namespace}}}text', attrib=attrib, nsmap=root.nsmap) root.append(ans) return ans def create_ext_text_record(self, root, bl_id, lpath, thumbnail): namespace = root.nsmap[None] attrib = {'path': lpath} - ans = root.makeelement('{%s}text'%namespace, attrib=attrib, + ans = root.makeelement(f'{{{namespace}}}text', attrib=attrib, nsmap=root.nsmap) ans.tail = '\n' if len(root) > 0: @@ -586,7 +586,7 @@ class XMLCache: root.append(ans) if thumbnail and thumbnail[-1]: ans.text = '\n' + '\t\t' - t = root.makeelement('{%s}thumbnail'%namespace, + t = root.makeelement(f'{{{namespace}}}thumbnail', attrib={'width':str(thumbnail[0]), 'height':str(thumbnail[1])}, nsmap=root.nsmap) t.text = 'main_thumbnail.jpg' @@ -757,7 +757,7 @@ class XMLCache: return m def book_by_lpath(self, lpath, root): - matches = root.xpath('//*[local-name()="text" and @path="%s"]'%lpath) + matches = root.xpath(f'//*[local-name()="text" and @path="{lpath}"]') if matches: return matches[0] @@ -782,7 +782,7 @@ class XMLCache: for i in self.roots: for c in ('library', 'text', 'image', 'playlist', 'thumbnail', 'watchSpecial'): - matches = self.record_roots[i].xpath('//*[local-name()="%s"]'%c) + matches = self.record_roots[i].xpath(f'//*[local-name()="{c}"]') if matches: e = matches[0] self.namespaces[i] = e.nsmap[e.prefix] diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 2e678df3dc..c3dd753b48 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -316,11 +316,11 @@ class PRST1(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The SONY database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) def get_lastrowid(self, cursor): @@ -374,11 +374,11 @@ class PRST1(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The SONY database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) # Get the books themselves, but keep track of any that are less than the minimum. @@ -546,11 +546,11 @@ class PRST1(USBMS): import traceback tb = traceback.format_exc() raise DeviceError((('The SONY database is corrupted. ' - ' Delete the file %s on your reader and then disconnect ' + f' Delete the file {dbpath} on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' - ' any notes/highlights, etc.')%dbpath)+' Underlying error:' + ' any notes/highlights, etc.'))+' Underlying error:' '\n'+tb) db_collections = {} diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 3191b69052..e9b288d83a 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -45,11 +45,9 @@ class USBDevice(_USBDevice): return self def __repr__(self): - return ('USBDevice(busnum=%s, devnum=%s, ' - 'vendor_id=0x%04x, product_id=0x%04x, bcd=0x%04x, ' - 'manufacturer=%s, product=%s, serial=%s)')%( - self.busnum, self.devnum, self.vendor_id, self.product_id, - self.bcd, self.manufacturer, self.product, self.serial) + return (f'USBDevice(busnum={self.busnum}, devnum={self.devnum}, ' + f'vendor_id=0x{self.vendor_id:04x}, product_id=0x{self.product_id:04x}, bcd=0x{self.bcd:04x}, ' + f'manufacturer={self.manufacturer}, product={self.product}, serial={self.serial})') __str__ = __repr__ __unicode__ = __repr__ diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 123fff02cd..315fab2ad5 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -402,8 +402,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): return total_elapsed = time.time() - self.debug_start_time elapsed = time.time() - self.debug_time - print('SMART_DEV (%7.2f:%7.3f) %s'%(total_elapsed, elapsed, - inspect.stack()[1][3]), end='') + print(f'SMART_DEV ({total_elapsed:7.2f}:{elapsed:7.3f}) {inspect.stack()[1][3]}', end='') for a in args: try: if isinstance(a, dict): @@ -712,7 +711,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): wait_for_response=self.can_send_ok_to_sendbook) if self.can_send_ok_to_sendbook: if opcode == 'ERROR': - raise UserFeedback(msg='Sending book %s to device failed' % lpath, + raise UserFeedback(msg=f'Sending book {lpath} to device failed', details=result.get('message', ''), level=UserFeedback.ERROR) return @@ -1493,7 +1492,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): book = SDBook(self.PREFIX, lpath, other=mdata) length, lpath = self._put_file(infile, lpath, book, i, len(files)) if length < 0: - raise ControlError(desc='Sending book %s to device failed' % lpath) + raise ControlError(desc=f'Sending book {lpath} to device failed') paths.append((lpath, length)) # No need to deal with covers. The client will get the thumbnails # in the mi structure diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index eb08cb6525..24512f0769 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -256,7 +256,7 @@ class Device(DeviceConfig, DevicePlugin): if dl in dlmap['readonly_drives']: filtered.add(dl) if debug: - prints('Ignoring the drive %s as it is readonly' % dl) + prints(f'Ignoring the drive {dl} as it is readonly') elif self.windows_filter_pnp_id(pnp_id): filtered.add(dl) if debug: @@ -264,7 +264,7 @@ class Device(DeviceConfig, DevicePlugin): elif not drive_is_ok(dl, debug=debug): filtered.add(dl) if debug: - prints('Ignoring the drive %s because failed to get free space for it' % dl) + prints(f'Ignoring the drive {dl} because failed to get free space for it') dlmap['drive_letters'] = [dl for dl in dlmap['drive_letters'] if dl not in filtered] if not dlmap['drive_letters']: diff --git a/src/calibre/devices/usbms/deviceconfig.py b/src/calibre/devices/usbms/deviceconfig.py index 6d488f631d..57b263254b 100644 --- a/src/calibre/devices/usbms/deviceconfig.py +++ b/src/calibre/devices/usbms/deviceconfig.py @@ -57,7 +57,7 @@ class DeviceConfig: @classmethod def _config(cls): name = cls._config_base_name() - c = Config('device_drivers_%s' % name, _('settings for device drivers')) + c = Config(f'device_drivers_{name}', _('settings for device drivers')) c.add_opt('format_map', default=cls.FORMATS, help=_('Ordered list of formats the device will accept')) c.add_opt('use_subdirs', default=cls.SUPPORTS_SUB_DIRS_DEFAULT, diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 9810fc9d4f..e138c81c61 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -47,7 +47,7 @@ def safe_walk(top, topdown=True, onerror=None, followlinks=False, maxdepth=128): try: name = name.decode(filesystem_encoding) except UnicodeDecodeError: - debug_print('Skipping undecodeable file: %r' % name) + debug_print(f'Skipping undecodeable file: {name!r}') continue if isdir(join(top, name)): dirs.append(name) diff --git a/src/calibre/devices/utils.py b/src/calibre/devices/utils.py index 56ce60e81c..ada41e79a8 100644 --- a/src/calibre/devices/utils.py +++ b/src/calibre/devices/utils.py @@ -60,7 +60,7 @@ def build_template_regexp(template): template = template.rpartition('/')[2] return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)') except: - prints('Failed to parse template: %r'%template) + prints(f'Failed to parse template: {template!r}') template = '{title} - {authors}' return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)') diff --git a/src/calibre/devices/winusb.py b/src/calibre/devices/winusb.py index 6a17123ac5..bc5569a965 100644 --- a/src/calibre/devices/winusb.py +++ b/src/calibre/devices/winusb.py @@ -69,8 +69,8 @@ class GUID(Structure): self.data1, self.data2, self.data3, - ''.join(['%02x' % d for d in self.data4[:2]]), - ''.join(['%02x' % d for d in self.data4[2:]]), + ''.join([f'{d:02x}' for d in self.data4[:2]]), + ''.join([f'{d:02x}' for d in self.data4[2:]]), ) @@ -394,7 +394,7 @@ def cwrap(name, restype, *argtypes, **kw): lib = cfgmgr if name.startswith('CM') else setupapi func = prototype((name, kw.pop('lib', lib))) if kw: - raise TypeError('Unknown keyword arguments: %r' % kw) + raise TypeError(f'Unknown keyword arguments: {kw!r}') if errcheck is not None: func.errcheck = errcheck return func @@ -414,7 +414,7 @@ def bool_err_check(result, func, args): def config_err_check(result, func, args): if result != CR_CODES['CR_SUCCESS']: - raise WinError(result, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(result, result)) + raise WinError(result, f'The cfgmgr32 function failed with err: {CR_CODE_NAMES.get(result, result)}') return args @@ -575,7 +575,7 @@ def get_device_id(devinst, buf=None): buf = create_unicode_buffer(devid_size.value) continue if ret != CR_CODES['CR_SUCCESS']: - raise WinError(ret, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(ret, ret)) + raise WinError(ret, f'The cfgmgr32 function failed with err: {CR_CODE_NAMES.get(ret, ret)}') break return wstring_at(buf), buf @@ -610,7 +610,7 @@ def convert_registry_data(raw, size, dtype): if size == 0: return 0 return cast(raw, POINTER(QWORD)).contents.value - raise ValueError('Unsupported data type: %r' % dtype) + raise ValueError(f'Unsupported data type: {dtype!r}') def get_device_registry_property(dev_list, p_devinfo, property_type=SPDRP_HARDWAREID, buf=None): @@ -712,9 +712,8 @@ class USBDevice(_USBDevice): def r(x): if x is None: return 'None' - return '0x%x' % x - return 'USBDevice(vendor_id={} product_id={} bcd={} devid={} devinst={})'.format( - r(self.vendor_id), r(self.product_id), r(self.bcd), self.devid, self.devinst) + return f'0x{x:x}' + return f'USBDevice(vendor_id={r(self.vendor_id)} product_id={r(self.product_id)} bcd={r(self.bcd)} devid={self.devid} devinst={self.devinst})' def parse_hex(x): @@ -976,7 +975,7 @@ def get_device_string(hub_handle, device_port, index, buf=None, lang=0x409): data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data sz, dtype = data.bLength, data.bType if dtype != 0x03: - raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype) + raise OSError(errno.EINVAL, f'Invalid datatype for string descriptor: 0x{dtype:x}') return buf, wstring_at(addressof(data.String), sz // 2).rstrip('\0') @@ -996,7 +995,7 @@ def get_device_languages(hub_handle, device_port, buf=None): data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data sz, dtype = data.bLength, data.bType if dtype != 0x03: - raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype) + raise OSError(errno.EINVAL, f'Invalid datatype for string descriptor: 0x{dtype:x}') data = cast(data.String, POINTER(USHORT*(sz//2))) return buf, list(filter(None, data.contents)) diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index 7fe8ab3dfc..a6db1bd73f 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -245,7 +245,7 @@ def escape_xpath_attr(value): if x: q = "'" if '"' in x else '"' ans.append(q + x + q) - return 'concat(%s)' % ', '.join(ans) + return 'concat({})'.format(', '.join(ans)) else: - return "'%s'" % value - return '"%s"' % value + return f"'{value}'" + return f'"{value}"' diff --git a/src/calibre/ebooks/chm/reader.py b/src/calibre/ebooks/chm/reader.py index b4b0d8efa6..d4561718f9 100644 --- a/src/calibre/ebooks/chm/reader.py +++ b/src/calibre/ebooks/chm/reader.py @@ -55,7 +55,7 @@ class CHMReader(CHMFile): t.write(open(input, 'rb').read()) input = t.name if not self.LoadCHM(input): - raise CHMError("Unable to open CHM file '%s'"%(input,)) + raise CHMError(f"Unable to open CHM file '{input}'") self.log = log self.input_encoding = input_encoding self._sourcechm = input @@ -188,7 +188,7 @@ class CHMReader(CHMFile): try: data = self.GetFile(path) except: - self.log.exception('Failed to extract %s from CHM, ignoring'%path) + self.log.exception(f'Failed to extract {path} from CHM, ignoring') continue if lpath.find(';') != -1: # fix file names with ";" at the end, see _reformat() @@ -203,7 +203,7 @@ class CHMReader(CHMFile): pass except: if iswindows and len(lpath) > 250: - self.log.warn('%r filename too long, skipping'%path) + self.log.warn(f'{path!r} filename too long, skipping') continue raise diff --git a/src/calibre/ebooks/compression/tcr.py b/src/calibre/ebooks/compression/tcr.py index e5ffb65bc0..839f7d6328 100644 --- a/src/calibre/ebooks/compression/tcr.py +++ b/src/calibre/ebooks/compression/tcr.py @@ -119,7 +119,7 @@ def decompress(stream): txt = [] stream.seek(0) if stream.read(9) != b'!!8-Bit!!': - raise ValueError('File %s contains an invalid TCR header.' % stream.name) + raise ValueError(f'File {stream.name} contains an invalid TCR header.') # Codes that the file contents are broken down into. entries = [] diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 54e6e86cbd..8e46584764 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -371,8 +371,7 @@ def read_sr_patterns(path, log=None): try: re.compile(line) except: - msg = 'Invalid regular expression: %r from file: %r'%( - line, path) + msg = f'Invalid regular expression: {line!r} from file: {path!r}' if log is not None: log.error(msg) raise SystemExit(1) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index 7168985f93..80b279ea95 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -23,7 +23,7 @@ class CHMInput(InputFormatPlugin): from calibre.ebooks.chm.reader import CHMReader log.debug('Opening CHM file') rdr = CHMReader(chm_path, log, input_encoding=self.opts.input_encoding) - log.debug('Extracting CHM to %s' % output_dir) + log.debug(f'Extracting CHM to {output_dir}') rdr.extract_content(output_dir, debug_dump=debug_dump) self._chm_reader = rdr return rdr.hhc_path @@ -46,8 +46,8 @@ class CHMInput(InputFormatPlugin): # closing stream so CHM can be opened by external library stream.close() - log.debug('tdir=%s' % tdir) - log.debug('stream.name=%s' % stream.name) + log.debug(f'tdir={tdir}') + log.debug(f'stream.name={stream.name}') debug_dump = False odi = options.debug_pipeline if odi: diff --git a/src/calibre/ebooks/conversion/plugins/comic_input.py b/src/calibre/ebooks/conversion/plugins/comic_input.py index c07bac599e..c2318cdc94 100644 --- a/src/calibre/ebooks/conversion/plugins/comic_input.py +++ b/src/calibre/ebooks/conversion/plugins/comic_input.py @@ -99,10 +99,9 @@ class ComicInput(InputFormatPlugin): comics = [] with CurrentDir(tdir): if not os.path.exists('comics.txt'): - raise ValueError(( - '%s is not a valid comic collection' + raise ValueError( + f'{stream.name} is not a valid comic collection' ' no comics.txt was found in the file') - %stream.name) with open('comics.txt', 'rb') as f: raw = f.read() if raw.startswith(codecs.BOM_UTF16_BE): @@ -125,7 +124,7 @@ class ComicInput(InputFormatPlugin): if os.access(fname, os.R_OK): comics.append([title, fname]) if not comics: - raise ValueError('%s has no comics'%stream.name) + raise ValueError(f'{stream.name} has no comics') return comics def get_pages(self, comic, tdir2): @@ -135,12 +134,11 @@ class ComicInput(InputFormatPlugin): verbose=self.opts.verbose) thumbnail = None if not new_pages: - raise ValueError('Could not find any pages in the comic: %s' - %comic) + raise ValueError(f'Could not find any pages in the comic: {comic}') if self.opts.no_process: n2 = [] for i, page in enumerate(new_pages): - n2.append(os.path.join(tdir2, '{} - {}' .format(i, os.path.basename(page)))) + n2.append(os.path.join(tdir2, f'{i} - {os.path.basename(page)}')) shutil.copyfile(page, n2[-1]) new_pages = n2 else: @@ -152,8 +150,7 @@ class ComicInput(InputFormatPlugin): for f in failures: self.log.warning('\t', f) if not new_pages: - raise ValueError('Could not find any valid pages in comic: %s' - % comic) + raise ValueError(f'Could not find any valid pages in comic: {comic}') thumbnail = os.path.join(tdir2, 'thumbnail.'+self.opts.output_format.lower()) if not os.access(thumbnail, os.R_OK): @@ -193,7 +190,7 @@ class ComicInput(InputFormatPlugin): comics.append((title, pages, wrappers)) if not comics: - raise ValueError('No comic pages found in %s'%stream.name) + raise ValueError(f'No comic pages found in {stream.name}') mi = MetaInformation(os.path.basename(stream.name).rpartition('.')[0], [_('Unknown')]) @@ -299,8 +296,8 @@ class ComicInput(InputFormatPlugin): pages = '\n'.join(page(i, src) for i, src in enumerate(pages)) base = os.path.dirname(pages[0]) - wrapper = ''' - + wrapper = f''' + - {} + {pages} - '''.format(XHTML_NS, pages) + ''' path = os.path.join(base, cdir, 'wrapper.xhtml') with open(path, 'wb') as f: f.write(wrapper.encode('utf-8')) diff --git a/src/calibre/ebooks/conversion/plugins/epub_input.py b/src/calibre/ebooks/conversion/plugins/epub_input.py index c3f530f259..ea4298d2c0 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_input.py +++ b/src/calibre/ebooks/conversion/plugins/epub_input.py @@ -281,7 +281,7 @@ class EPUBInput(InputFormatPlugin): path = getattr(stream, 'name', 'stream') if opf is None: - raise ValueError('%s is not a valid EPUB file (could not find opf)'%path) + raise ValueError(f'{path} is not a valid EPUB file (could not find opf)') opf = os.path.relpath(opf, os.getcwd()) parts = os.path.split(opf) @@ -369,7 +369,7 @@ class EPUBInput(InputFormatPlugin): root = parse(raw, log=log) ncx = safe_xml_fromstring('') navmap = ncx[0] - et = '{%s}type' % EPUB_NS + et = f'{{{EPUB_NS}}}type' bn = os.path.basename(nav_path) def add_from_li(li, parent): diff --git a/src/calibre/ebooks/conversion/plugins/epub_output.py b/src/calibre/ebooks/conversion/plugins/epub_output.py index d2554bedec..eef51567c6 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_output.py +++ b/src/calibre/ebooks/conversion/plugins/epub_output.py @@ -335,7 +335,7 @@ class EPUBOutput(OutputFormatPlugin): key = re.sub(r'[^a-fA-F0-9]', '', uuid) if len(key) < 16: - raise ValueError('UUID identifier %r is invalid'%uuid) + raise ValueError(f'UUID identifier {uuid!r} is invalid') key = bytearray(from_hex_bytes((key + key)[:32])) paths = [] with CurrentDir(tdir): @@ -362,10 +362,10 @@ class EPUBOutput(OutputFormatPlugin): - + - '''%(uri.replace('"', '\\"'))) + '''.format(uri.replace('"', '\\"'))) if fonts: ans = ''' tag detected') html = separate_paragraphs_single_line(pre.text) html = convert_basic(html).replace('', - ''%XHTML_NS) + f'') html = xml_to_unicode(html, strip_encoding_pats=True, resolve_entities=True)[0] if opts.smarten_punctuation: diff --git a/src/calibre/ebooks/conversion/plugins/lrf_input.py b/src/calibre/ebooks/conversion/plugins/lrf_input.py index b357fa4f7f..1ae8b3d38b 100644 --- a/src/calibre/ebooks/conversion/plugins/lrf_input.py +++ b/src/calibre/ebooks/conversion/plugins/lrf_input.py @@ -39,19 +39,18 @@ class LRFInput(InputFormatPlugin): char_button_map = {} for x in doc.xpath('//CharButton[@refobj]'): ro = x.get('refobj') - jump_button = doc.xpath('//*[@objid="%s"]'%ro) + jump_button = doc.xpath(f'//*[@objid="{ro}"]') if jump_button: jump_to = jump_button[0].xpath('descendant::JumpTo[@refpage and @refobj]') if jump_to: - char_button_map[ro] = '%s.xhtml#%s'%(jump_to[0].get('refpage'), + char_button_map[ro] = '{}.xhtml#{}'.format(jump_to[0].get('refpage'), jump_to[0].get('refobj')) plot_map = {} for x in doc.xpath('//Plot[@refobj]'): ro = x.get('refobj') - image = doc.xpath('//Image[@objid="%s" and @refstream]'%ro) + image = doc.xpath(f'//Image[@objid="{ro}" and @refstream]') if image: - imgstr = doc.xpath('//ImageStream[@objid="%s" and @file]'% - image[0].get('refstream')) + imgstr = doc.xpath('//ImageStream[@objid="{}" and @file]'.format(image[0].get('refstream'))) if imgstr: plot_map[ro] = imgstr[0].get('file') diff --git a/src/calibre/ebooks/conversion/plugins/lrf_output.py b/src/calibre/ebooks/conversion/plugins/lrf_output.py index dfcc15dcb4..bf5a1623db 100644 --- a/src/calibre/ebooks/conversion/plugins/lrf_output.py +++ b/src/calibre/ebooks/conversion/plugins/lrf_output.py @@ -153,7 +153,7 @@ class LRFOutput(OutputFormatPlugin): ps['textheight'] = height book = Book(title=opts.title, author=opts.author, bookid=uuid4().hex, - publisher='%s %s'%(__appname__, __version__), + publisher=f'{__appname__} {__version__}', category=_('Comic'), pagestyledefault=ps, booksetting=BookSetting(screenwidth=width, screenheight=height)) for page in pages: diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index d0a1fd74ed..177df9f0bd 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -37,7 +37,7 @@ class MOBIInput(InputFormatPlugin): mr.extract_content('.', parse_cache) if mr.kf8_type is not None: - log('Found KF8 MOBI of type %r'%mr.kf8_type) + log(f'Found KF8 MOBI of type {mr.kf8_type!r}') if mr.kf8_type == 'joint': self.mobi_is_joint = True from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader diff --git a/src/calibre/ebooks/conversion/plugins/oeb_output.py b/src/calibre/ebooks/conversion/plugins/oeb_output.py index e037b91292..c106b2b508 100644 --- a/src/calibre/ebooks/conversion/plugins/oeb_output.py +++ b/src/calibre/ebooks/conversion/plugins/oeb_output.py @@ -83,7 +83,7 @@ class OEBOutput(OutputFormatPlugin): def manifest_items_with_id(id_): return root.xpath('//*[local-name() = "manifest"]/*[local-name() = "item" ' - ' and @id="%s"]'%id_) + f' and @id="{id_}"]') if len(cov) == 1: cov = cov[0] diff --git a/src/calibre/ebooks/conversion/plugins/pdb_input.py b/src/calibre/ebooks/conversion/plugins/pdb_input.py index 1e6442dfcf..a20b43db55 100644 --- a/src/calibre/ebooks/conversion/plugins/pdb_input.py +++ b/src/calibre/ebooks/conversion/plugins/pdb_input.py @@ -24,8 +24,7 @@ class PDBInput(InputFormatPlugin): Reader = get_reader(header.ident) if Reader is None: - raise PDBError('No reader available for format within container.\n Identity is %s. Book type is %s' % - (header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown')))) + raise PDBError('No reader available for format within container.\n Identity is {}. Book type is {}'.format(header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown')))) log.debug(f'Detected ebook format as: {IDENTITY_TO_NAME[header.ident]} with identity: {header.ident}') diff --git a/src/calibre/ebooks/conversion/plugins/pdb_output.py b/src/calibre/ebooks/conversion/plugins/pdb_output.py index 305d729353..c55bf897a4 100644 --- a/src/calibre/ebooks/conversion/plugins/pdb_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdb_output.py @@ -44,7 +44,7 @@ class PDBOutput(OutputFormatPlugin): Writer = get_writer(opts.format) if Writer is None: - raise PDBError('No writer available for format %s.' % format) + raise PDBError(f'No writer available for format {format}.') setattr(opts, 'max_line_length', 0) setattr(opts, 'force_max_line_length', False) diff --git a/src/calibre/ebooks/conversion/plugins/pml_input.py b/src/calibre/ebooks/conversion/plugins/pml_input.py index c4d241f965..3c9300861c 100644 --- a/src/calibre/ebooks/conversion/plugins/pml_input.py +++ b/src/calibre/ebooks/conversion/plugins/pml_input.py @@ -47,7 +47,7 @@ class PMLInput(InputFormatPlugin): self.log.debug('Converting PML to HTML...') hizer = PML_HTMLizer() html = hizer.parse_pml(pml_stream.read().decode(ienc), html_path) - html = '%s'%html + html = f'{html}' html_stream.write(html.encode('utf-8', 'replace')) if pclose: @@ -106,7 +106,7 @@ class PMLInput(InputFormatPlugin): html_path = os.path.join(os.getcwd(), html_name) pages.append(html_name) - log.debug('Processing PML item %s...' % pml) + log.debug(f'Processing PML item {pml}...') ttoc = self.process_pml(pml, html_path) toc += ttoc images = self.get_images(stream, tdir, True) diff --git a/src/calibre/ebooks/conversion/plugins/recipe_input.py b/src/calibre/ebooks/conversion/plugins/recipe_input.py index af78daf3ec..9ebac38d85 100644 --- a/src/calibre/ebooks/conversion/plugins/recipe_input.py +++ b/src/calibre/ebooks/conversion/plugins/recipe_input.py @@ -111,8 +111,7 @@ class RecipeInput(InputFormatPlugin): self.recipe_source = raw if recipe.requires_version > numeric_version: log.warn( - 'Downloaded recipe needs calibre version at least: %s' % - ('.'.join(recipe.requires_version))) + 'Downloaded recipe needs calibre version at least: {}'.format('.'.join(recipe.requires_version))) builtin = True except: log.exception('Failed to compile downloaded recipe. Falling ' @@ -130,8 +129,7 @@ class RecipeInput(InputFormatPlugin): log('Using downloaded builtin recipe') if recipe is None: - raise ValueError('%r is not a valid recipe file or builtin recipe' % - recipe_or_file) + raise ValueError(f'{recipe_or_file!r} is not a valid recipe file or builtin recipe') disabled = getattr(recipe, 'recipe_disabled', None) if disabled is not None: diff --git a/src/calibre/ebooks/conversion/plugins/rtf_input.py b/src/calibre/ebooks/conversion/plugins/rtf_input.py index 982d5fb0e3..56f7a50f7d 100644 --- a/src/calibre/ebooks/conversion/plugins/rtf_input.py +++ b/src/calibre/ebooks/conversion/plugins/rtf_input.py @@ -164,7 +164,7 @@ class RTFInput(InputFormatPlugin): try: return self.rasterize_wmf(name) except Exception: - self.log.exception('Failed to convert WMF image %r'%name) + self.log.exception(f'Failed to convert WMF image {name!r}') return self.replace_wmf(name) def replace_wmf(self, name): @@ -217,7 +217,7 @@ class RTFInput(InputFormatPlugin): css += '\n' +'\n'.join(color_classes) for cls, val in iteritems(border_styles): - css += '\n\n.%s {\n%s\n}'%(cls, val) + css += f'\n\n.{cls} {{\n{val}\n}}' with open('styles.css', 'ab') as f: f.write(css.encode('utf-8')) @@ -229,16 +229,16 @@ class RTFInput(InputFormatPlugin): style = ['border-style: hidden', 'border-width: 1px', 'border-color: black'] for x in ('bottom', 'top', 'left', 'right'): - bs = elem.get('border-cell-%s-style'%x, None) + bs = elem.get(f'border-cell-{x}-style', None) if bs: cbs = border_style_map.get(bs, 'solid') - style.append('border-%s-style: %s'%(x, cbs)) - bw = elem.get('border-cell-%s-line-width'%x, None) + style.append(f'border-{x}-style: {cbs}') + bw = elem.get(f'border-cell-{x}-line-width', None) if bw: - style.append('border-%s-width: %spt'%(x, bw)) - bc = elem.get('border-cell-%s-color'%x, None) + style.append(f'border-{x}-width: {bw}pt') + bc = elem.get(f'border-cell-{x}-color', None) if bc: - style.append('border-%s-color: %s'%(x, bc)) + style.append(f'border-{x}-color: {bc}') style = ';\n'.join(style) if style not in border_styles: border_styles.append(style) diff --git a/src/calibre/ebooks/conversion/plugins/snb_input.py b/src/calibre/ebooks/conversion/plugins/snb_input.py index 95f08d9b20..64273cc2e7 100644 --- a/src/calibre/ebooks/conversion/plugins/snb_input.py +++ b/src/calibre/ebooks/conversion/plugins/snb_input.py @@ -98,9 +98,9 @@ class SNBInput(InputFormatPlugin): lines = [] for line in snbc.find('.//body'): if line.tag == 'text': - lines.append('

%s

' % html_encode(line.text)) + lines.append(f'

{html_encode(line.text)}

') elif line.tag == 'img': - lines.append('

' % html_encode(line.text)) + lines.append(f'

') with open(os.path.join(tdir, fname), 'wb') as f: f.write((HTML_TEMPLATE % (chapterName, '\n'.join(lines))).encode('utf-8', 'replace')) oeb.toc.add(ch.text, fname) diff --git a/src/calibre/ebooks/conversion/plugins/snb_output.py b/src/calibre/ebooks/conversion/plugins/snb_output.py index cfffbde523..3c1f26d5b9 100644 --- a/src/calibre/ebooks/conversion/plugins/snb_output.py +++ b/src/calibre/ebooks/conversion/plugins/snb_output.py @@ -141,7 +141,7 @@ class SNBOutput(OutputFormatPlugin): if tocitem.href.find('#') != -1: item = tocitem.href.split('#') if len(item) != 2: - log.error('Error in TOC item: %s' % tocitem) + log.error(f'Error in TOC item: {tocitem}') else: if item[0] in outputFiles: outputFiles[item[0]].append((item[1], tocitem.title)) @@ -176,16 +176,16 @@ class SNBOutput(OutputFormatPlugin): from calibre.ebooks.oeb.base import OEB_DOCS, OEB_IMAGES if m.hrefs[item.href].media_type in OEB_DOCS: if item.href not in outputFiles: - log.debug('File %s is unused in TOC. Continue in last chapter' % item.href) + log.debug(f'File {item.href} is unused in TOC. Continue in last chapter') mergeLast = True else: if oldTree is not None and mergeLast: - log.debug('Output the modified chapter again: %s' % lastName) + log.debug(f'Output the modified chapter again: {lastName}') with open(os.path.join(snbcDir, lastName), 'wb') as f: f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8')) mergeLast = False - log.debug('Converting %s to snbc...' % item.href) + log.debug(f'Converting {item.href} to snbc...') snbwriter = SNBMLizer(log) snbcTrees = None if not mergeLast: @@ -199,11 +199,11 @@ class SNBOutput(OutputFormatPlugin): with open(os.path.join(snbcDir, lastName), 'wb') as f: f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8')) else: - log.debug('Merge %s with last TOC item...' % item.href) + log.debug(f'Merge {item.href} with last TOC item...') snbwriter.merge_content(oldTree, oeb_book, item, [('', _('Start'))], opts) # Output the last one if needed - log.debug('Output the last modified chapter again: %s' % lastName) + log.debug(f'Output the last modified chapter again: {lastName}') if oldTree is not None and mergeLast: with open(os.path.join(snbcDir, lastName), 'wb') as f: f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8')) @@ -211,7 +211,7 @@ class SNBOutput(OutputFormatPlugin): for item in m: if m.hrefs[item.href].media_type in OEB_IMAGES: - log.debug('Converting image: %s ...' % item.href) + log.debug(f'Converting image: {item.href} ...') content = m.hrefs[item.href].data # Convert & Resize image self.HandleImage(content, os.path.join(snbiDir, ProcessFileName(item.href))) diff --git a/src/calibre/ebooks/conversion/plugins/txt_input.py b/src/calibre/ebooks/conversion/plugins/txt_input.py index c4d620720d..d6d43a644c 100644 --- a/src/calibre/ebooks/conversion/plugins/txt_input.py +++ b/src/calibre/ebooks/conversion/plugins/txt_input.py @@ -198,13 +198,13 @@ class TXTInput(InputFormatPlugin): if file_ext in {'md', 'textile', 'markdown'}: options.formatting_type = {'md': 'markdown'}.get(file_ext, file_ext) log.info('File extension indicates particular formatting. ' - 'Forcing formatting type to: %s'%options.formatting_type) + f'Forcing formatting type to: {options.formatting_type}') options.paragraph_type = 'off' # Get the encoding of the document. if options.input_encoding: ienc = options.input_encoding - log.debug('Using user specified input encoding of %s' % ienc) + log.debug(f'Using user specified input encoding of {ienc}') else: det_encoding = detect(txt[:4096]) det_encoding, confidence = det_encoding['encoding'], det_encoding['confidence'] @@ -218,7 +218,7 @@ class TXTInput(InputFormatPlugin): log.debug(f'Detected input encoding as {ienc} with a confidence of {confidence * 100}%') if not ienc: ienc = 'utf-8' - log.debug('No input encoding specified and could not auto detect using %s' % ienc) + log.debug(f'No input encoding specified and could not auto detect using {ienc}') # Remove BOM from start of txt as its presence can confuse markdown import codecs for bom in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE, codecs.BOM_UTF8, codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): @@ -240,12 +240,12 @@ class TXTInput(InputFormatPlugin): log.debug('Could not reliably determine paragraph type using block') options.paragraph_type = 'block' else: - log.debug('Auto detected paragraph type as %s' % options.paragraph_type) + log.debug(f'Auto detected paragraph type as {options.paragraph_type}') # Detect formatting if options.formatting_type == 'auto': options.formatting_type = detect_formatting_type(txt) - log.debug('Auto detected formatting as %s' % options.formatting_type) + log.debug(f'Auto detected formatting as {options.formatting_type}') if options.formatting_type == 'heuristic': setattr(options, 'enable_heuristics', True) diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index b7c562509c..1482347c9b 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -945,7 +945,7 @@ OptionRecommendation(name='search_replace', from calibre import browser from calibre.ptempfile import PersistentTemporaryFile - self.log('Downloading cover from %r'%url) + self.log(f'Downloading cover from {url!r}') br = browser() raw = br.open_novisit(url).read() buf = io.BytesIO(raw) @@ -999,7 +999,7 @@ OptionRecommendation(name='search_replace', setattr(self.opts, attr, x) return self.log.warn( - 'Profile (%s) %r is no longer available, using default'%(which, sval)) + f'Profile ({which}) {sval!r} is no longer available, using default') for x in profiles(): if x.short_name == 'default': setattr(self.opts, attr, x) @@ -1017,7 +1017,7 @@ OptionRecommendation(name='search_replace', self.log('Conversion options changed from defaults:') for rec in self.changed_options: if rec.option.name not in ('username', 'password'): - self.log(' ', '%s:' % rec.option.name, repr(rec.recommended_value)) + self.log(' ', f'{rec.option.name}:', repr(rec.recommended_value)) if self.opts.verbose > 1: self.log.debug('Resolved conversion options') try: @@ -1204,7 +1204,7 @@ OptionRecommendation(name='search_replace', try: fkey = list(map(float, fkey.split(','))) except Exception: - self.log.error('Invalid font size key: %r ignoring'%fkey) + self.log.error(f'Invalid font size key: {fkey!r} ignoring') fkey = self.opts.dest.fkey from calibre.ebooks.oeb.transforms.jacket import Jacket @@ -1298,7 +1298,7 @@ OptionRecommendation(name='search_replace', self.dump_oeb(self.oeb, out_dir) self.log('Processed HTML written to:', out_dir) - self.log.info('Creating %s...'%self.output_plugin.name) + self.log.info(f'Creating {self.output_plugin.name}...') our = CompositeProgressReporter(0.67, 1., self.ui_reporter) self.output_plugin.report_progress = our our(0., _('Running %s plugin')%self.output_plugin.name) diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 6815967380..1a01cc570e 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -41,7 +41,7 @@ _ligpat = re.compile('|'.join(LIGATURES)) def sanitize_head(match): x = match.group(1).strip() x = _span_pat.sub('', x) - return '\n%s\n' % x + return f'\n{x}\n' def chap_head(match): @@ -200,12 +200,12 @@ class Dehyphenator: "((ed)?ly|'?e?s||a?(t|s)?ion(s|al(ly)?)?|ings?|er|(i)?ous|" "(i|a)ty|(it)?ies|ive|gence|istic(ally)?|(e|a)nce|m?ents?|ism|ated|" "(e|u)ct(ed)?|ed|(i|ed)?ness|(e|a)ncy|ble|ier|al|ex|ian)$") - self.suffixes = re.compile(r'^%s' % self.suffix_string, re.IGNORECASE) - self.removesuffixes = re.compile(r'%s' % self.suffix_string, re.IGNORECASE) + self.suffixes = re.compile(rf'^{self.suffix_string}', re.IGNORECASE) + self.removesuffixes = re.compile(rf'{self.suffix_string}', re.IGNORECASE) # remove prefixes if the prefix was not already the point of hyphenation self.prefix_string = '^(dis|re|un|in|ex)' - self.prefixes = re.compile(r'%s$' % self.prefix_string, re.IGNORECASE) - self.removeprefix = re.compile(r'%s' % self.prefix_string, re.IGNORECASE) + self.prefixes = re.compile(rf'{self.prefix_string}$', re.IGNORECASE) + self.removeprefix = re.compile(rf'{self.prefix_string}', re.IGNORECASE) def dehyphenate(self, match): firsthalf = match.group('firstpart') @@ -295,10 +295,10 @@ class CSSPreProcessor: # Remove some of the broken CSS Microsoft products # create MS_PAT = re.compile(r''' - (?P^|;|\{)\s* # The end of the previous rule or block start - (%s).+? # The invalid selectors - (?P$|;|\}) # The end of the declaration - '''%'mso-|panose-|text-underline|tab-interval', + (?P^|;|\{{)\s* # The end of the previous rule or block start + ({}).+? # The invalid selectors + (?P$|;|\}}) # The end of the declaration + '''.format('mso-|panose-|text-underline|tab-interval'), re.MULTILINE|re.IGNORECASE|re.VERBOSE) def ms_sub(self, match): @@ -433,13 +433,13 @@ def book_designer_rules(): lambda match : ' '), # Create header tags (re.compile(r'<]*?id=BookTitle[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?', re.IGNORECASE), - lambda match : '

%s

'%(match.group(2) if match.group(2) else 'center', match.group(3))), + lambda match : '

{}

'.format(match.group(2) if match.group(2) else 'center', match.group(3))), (re.compile(r'<]*?id=BookAuthor[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?', re.IGNORECASE), - lambda match : '

%s

'%(match.group(2) if match.group(2) else 'center', match.group(3))), + lambda match : '

{}

'.format(match.group(2) if match.group(2) else 'center', match.group(3))), (re.compile(r'<]*?id=title[^><]*?>(.*?)', re.IGNORECASE|re.DOTALL), - lambda match : '

%s

'%(match.group(1),)), + lambda match : f'

{match.group(1)}

'), (re.compile(r'<]*?id=subtitle[^><]*?>(.*?)', re.IGNORECASE|re.DOTALL), - lambda match : '

%s

'%(match.group(1),)), + lambda match : f'

{match.group(1)}

'), ] return ans @@ -494,8 +494,7 @@ class HTMLPreProcessor: rules.insert(0, (search_re, replace_txt)) user_sr_rules[(search_re, replace_txt)] = search_pattern except Exception as e: - self.log.error('Failed to parse %r regexp because %s' % - (search, as_unicode(e))) + self.log.error(f'Failed to parse {search!r} regexp because {as_unicode(e)}') # search / replace using the sr?_search / sr?_replace options for i in range(1, 4): @@ -572,9 +571,8 @@ class HTMLPreProcessor: except Exception as e: if rule in user_sr_rules: self.log.error( - 'User supplied search & replace rule: %s -> %s ' - 'failed with error: %s, ignoring.'%( - user_sr_rules[rule], rule[1], e)) + f'User supplied search & replace rule: {user_sr_rules[rule]} -> {rule[1]} ' + f'failed with error: {e}, ignoring.') else: raise @@ -595,10 +593,10 @@ class HTMLPreProcessor: # Handle broken XHTML w/ SVG (ugh) if 'svg:' in html and SVG_NS not in html: html = html.replace( - ')' + re.escape(word) + r'(?=\s|<)', '%s' % word, html) + html = re.sub(r'(?<=\s|>)' + re.escape(word) + r'(?=\s|<)', f'{word}', html) search_text = re.sub(r'(?s)]*>.*?', '', html) search_text = re.sub(r'<[^>]*>', '', search_text) @@ -183,7 +183,7 @@ class HeuristicProcessor: ital_string = str(match.group('words')) # self.log.debug("italicising "+str(match.group(0))+" with "+ital_string+"") try: - html = re.sub(re.escape(str(match.group(0))), '%s' % ital_string, html) + html = re.sub(re.escape(str(match.group(0))), f'{ital_string}', html) except OverflowError: # match.group(0) was too large to be compiled into a regex continue @@ -305,7 +305,7 @@ class HeuristicProcessor: chapter_marker = arg_ignorecase+init_lookahead+full_chapter_line+blank_lines+lp_n_lookahead_open+n_lookahead+lp_n_lookahead_close+ \ lp_opt_title_open+title_line_open+title_header_open+lp_title+title_header_close+title_line_close+lp_opt_title_close - chapdetect = re.compile(r'%s' % chapter_marker) + chapdetect = re.compile(rf'{chapter_marker}') if analyze: hits = len(chapdetect.findall(html)) @@ -383,9 +383,9 @@ class HeuristicProcessor: em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening - unwrap = re.compile('%s' % unwrap_regex, re.UNICODE) - em_en_unwrap = re.compile('%s' % em_en_unwrap_regex, re.UNICODE) - shy_unwrap = re.compile('%s' % shy_unwrap_regex, re.UNICODE) + unwrap = re.compile(f'{unwrap_regex}', re.UNICODE) + em_en_unwrap = re.compile(f'{em_en_unwrap_regex}', re.UNICODE) + shy_unwrap = re.compile(f'{shy_unwrap_regex}', re.UNICODE) if format == 'txt': content = unwrap.sub(' ', content) @@ -449,7 +449,7 @@ class HeuristicProcessor: for i in range(2): html = re.sub(r'\s*]*>\s*(]*>\s*){0,2}\s*\s*', ' ', html) html = re.sub( - r'\s*{open}\s*({open}\s*{close}\s*){{0,2}}\s*{close}'.format(open=open_fmt_pat, close=close_fmt_pat), ' ', html) + rf'\s*{open_fmt_pat}\s*({open_fmt_pat}\s*{close_fmt_pat}\s*){{0,2}}\s*{close_fmt_pat}', ' ', html) # delete surrounding divs from empty paragraphs html = re.sub(r']*>\s*]*>\s*

\s*', '

', html) # Empty heading tags @@ -560,7 +560,7 @@ class HeuristicProcessor: line_two = '(?P'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_open)+ \ r'\s*(?P.*?)'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_close)+')' div_break_candidate_pattern = line+r'\s*]*>\s*\s*'+line_two - div_break_candidate = re.compile(r'%s' % div_break_candidate_pattern, re.IGNORECASE|re.UNICODE) + div_break_candidate = re.compile(rf'{div_break_candidate_pattern}', re.IGNORECASE|re.UNICODE) def convert_div_softbreaks(match): init_is_paragraph = self.check_paragraph(match.group('init_content')) @@ -583,7 +583,7 @@ class HeuristicProcessor: def detect_scene_breaks(self, html): scene_break_regex = self.line_open+'(?!('+self.common_in_text_beginnings+'|.*?'+self.common_in_text_endings+ \ r'<))(?P((?P((?!\s)\W))\s*(?P=break_char)?){1,10})\s*'+self.line_close - scene_breaks = re.compile(r'%s' % scene_break_regex, re.IGNORECASE|re.UNICODE) + scene_breaks = re.compile(rf'{scene_break_regex}', re.IGNORECASE|re.UNICODE) html = scene_breaks.sub(self.scene_break_open+r'\g

', html) return html diff --git a/src/calibre/ebooks/covers.py b/src/calibre/ebooks/covers.py index 8b106f7663..b0120681f0 100644 --- a/src/calibre/ebooks/covers.py +++ b/src/calibre/ebooks/covers.py @@ -762,7 +762,7 @@ def test(scale=0.25): for r, color in enumerate(sorted(default_color_themes)): for c, style in enumerate(sorted(all_styles())): mi.series_index = c + 1 - mi.title = 'An algorithmic cover [%s]' % color + mi.title = f'An algorithmic cover [{color}]' prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) scale_cover(prefs, scale) img = generate_cover(mi, prefs=prefs, as_qimage=True) diff --git a/src/calibre/ebooks/djvu/djvubzzdec.py b/src/calibre/ebooks/djvu/djvubzzdec.py index d518cdee63..032b8f339a 100644 --- a/src/calibre/ebooks/djvu/djvubzzdec.py +++ b/src/calibre/ebooks/djvu/djvubzzdec.py @@ -85,7 +85,7 @@ class BZZDecoderError(Exception): self.msg = msg def __str__(self): - return 'BZZDecoderError: %s' % (self.msg) + return f'BZZDecoderError: {self.msg}' # This table has been designed for the ZPCoder diff --git a/src/calibre/ebooks/docx/block_styles.py b/src/calibre/ebooks/docx/block_styles.py index 258a244581..bf9910138f 100644 --- a/src/calibre/ebooks/docx/block_styles.py +++ b/src/calibre/ebooks/docx/block_styles.py @@ -39,7 +39,7 @@ inherit = Inherit() def binary_property(parent, name, XPath, get): - vals = XPath('./w:%s' % name)(parent) + vals = XPath(f'./w:{name}')(parent) if not vals: return inherit val = get(vals[0], 'w:val', 'on') @@ -108,7 +108,7 @@ border_edges = ('left', 'top', 'right', 'bottom', 'between') def read_single_border(parent, edge, XPath, get): color = style = width = padding = None - for elem in XPath('./w:%s' % edge)(parent): + for elem in XPath(f'./w:{edge}')(parent): c = get(elem, 'w:color') if c is not None: color = simple_color(c) @@ -145,20 +145,20 @@ def read_border(parent, dest, XPath, get, border_edges=border_edges, name='pBdr' def border_to_css(edge, style, css): - bs = getattr(style, 'border_%s_style' % edge) - bc = getattr(style, 'border_%s_color' % edge) - bw = getattr(style, 'border_%s_width' % edge) + bs = getattr(style, f'border_{edge}_style') + bc = getattr(style, f'border_{edge}_color') + bw = getattr(style, f'border_{edge}_width') if isinstance(bw, numbers.Number): # WebKit needs at least 1pt to render borders and 3pt to render double borders bw = max(bw, (3 if bs == 'double' else 1)) if bs is not inherit and bs is not None: - css['border-%s-style' % edge] = bs + css[f'border-{edge}-style'] = bs if bc is not inherit and bc is not None: - css['border-%s-color' % edge] = bc + css[f'border-{edge}-color'] = bc if bw is not inherit and bw is not None: if isinstance(bw, numbers.Number): - bw = '%.3gpt' % bw - css['border-%s-width' % edge] = bw + bw = f'{bw:.3g}pt' + css[f'border-{edge}-width'] = bw def read_indent(parent, dest, XPath, get): @@ -305,12 +305,12 @@ class Frame: else: if self.h_rule != 'auto': t = 'min-height' if self.h_rule == 'atLeast' else 'height' - ans[t] = '%.3gpt' % self.h + ans[t] = f'{self.h:.3g}pt' if self.w is not None: - ans['width'] = '%.3gpt' % self.w - ans['padding-top'] = ans['padding-bottom'] = '%.3gpt' % self.v_space + ans['width'] = f'{self.w:.3g}pt' + ans['padding-top'] = ans['padding-bottom'] = f'{self.v_space:.3g}pt' if self.wrap not in {None, 'none'}: - ans['padding-left'] = ans['padding-right'] = '%.3gpt' % self.h_space + ans['padding-left'] = ans['padding-right'] = f'{self.h_space:.3g}pt' if self.x_align is None: fl = 'left' if self.x/page.width < 0.5 else 'right' else: @@ -412,12 +412,12 @@ class ParagraphStyle: c['page-break-after'] = 'avoid' for edge in ('left', 'top', 'right', 'bottom'): border_to_css(edge, self, c) - val = getattr(self, 'padding_%s' % edge) + val = getattr(self, f'padding_{edge}') if val is not inherit: - c['padding-%s' % edge] = '%.3gpt' % val - val = getattr(self, 'margin_%s' % edge) + c[f'padding-{edge}'] = f'{val:.3g}pt' + val = getattr(self, f'margin_{edge}') if val is not inherit: - c['margin-%s' % edge] = val + c[f'margin-{edge}'] = val if self.line_height not in {inherit, '1'}: c['line-height'] = self.line_height @@ -426,7 +426,7 @@ class ParagraphStyle: val = getattr(self, x) if val is not inherit: if x == 'font_size': - val = '%.3gpt' % val + val = f'{val:.3g}pt' c[x.replace('_', '-')] = val ta = self.text_align if ta is not inherit: @@ -465,11 +465,11 @@ class ParagraphStyle: def apply_between_border(self): for prop in ('width', 'color', 'style'): - setattr(self, 'border_bottom_%s' % prop, getattr(self, 'border_between_%s' % prop)) + setattr(self, f'border_bottom_{prop}', getattr(self, f'border_between_{prop}')) def has_visible_border(self): for edge in border_edges[:-1]: - bw, bs = getattr(self, 'border_%s_width' % edge), getattr(self, 'border_%s_style' % edge) + bw, bs = getattr(self, f'border_{edge}_width'), getattr(self, f'border_{edge}_style') if bw is not inherit and bw and bs is not inherit and bs != 'none': return True return False diff --git a/src/calibre/ebooks/docx/char_styles.py b/src/calibre/ebooks/docx/char_styles.py index 8bc673eb91..abec7b9183 100644 --- a/src/calibre/ebooks/docx/char_styles.py +++ b/src/calibre/ebooks/docx/char_styles.py @@ -149,7 +149,7 @@ def read_font(parent, dest, XPath, get): for col in XPath('./w:rFonts')(parent): val = get(col, 'w:asciiTheme') if val: - val = '|%s|' % val + val = f'|{val}|' else: val = get(col, 'w:ascii') if val: @@ -168,7 +168,7 @@ def read_font_cs(parent, dest, XPath, get): for col in XPath('./w:rFonts')(parent): val = get(col, 'w:csTheme') if val: - val = '|%s|' % val + val = f'|{val}|' else: val = get(col, 'w:cs') if val: @@ -248,9 +248,9 @@ class RunStyle: for x in ('color', 'style', 'width'): val = getattr(self, 'border_'+x) if x == 'width' and val is not inherit: - val = '%.3gpt' % val + val = f'{val:.3g}pt' if val is not inherit: - ans['border-%s' % x] = val + ans[f'border-{x}'] = val def clear_border_css(self): for x in ('color', 'style', 'width'): @@ -282,7 +282,7 @@ class RunStyle: self.get_border_css(c) if self.padding is not inherit: - c['padding'] = '%.3gpt' % self.padding + c['padding'] = f'{self.padding:.3g}pt' for x in ('color', 'background_color'): val = getattr(self, x) @@ -292,10 +292,10 @@ class RunStyle: for x in ('letter_spacing', 'font_size'): val = getattr(self, x) if val is not inherit: - c[x.replace('_', '-')] = '%.3gpt' % val + c[x.replace('_', '-')] = f'{val:.3g}pt' if self.position is not inherit: - c['vertical-align'] = '%.3gpt' % self.position + c['vertical-align'] = f'{self.position:.3g}pt' if self.highlight is not inherit and self.highlight != 'transparent': c['background-color'] = self.highlight diff --git a/src/calibre/ebooks/docx/cleanup.py b/src/calibre/ebooks/docx/cleanup.py index a4fb122566..02da00c739 100644 --- a/src/calibre/ebooks/docx/cleanup.py +++ b/src/calibre/ebooks/docx/cleanup.py @@ -127,7 +127,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid): span[-1].tail = '\xa0' # Move
s outside paragraphs, if possible. - pancestor = XPath('|'.join('ancestor::%s[1]' % x for x in ('p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) + pancestor = XPath('|'.join(f'ancestor::{x}[1]' for x in ('p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) for hr in root.xpath('//span/hr'): p = pancestor(hr) if p: @@ -156,7 +156,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)) - for parent in root.xpath('//*[(%s)]' % ' or '.join('name()="%s"' % t for t in parents)): + 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 # that of the parent. @@ -172,7 +172,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid): # Remove unnecessary span tags that are the only child of a parent block # element - for parent in root.xpath('//*[(%s) and count(span)=1]' % ' or '.join('name()="%s"' % t for t in parents)): + for parent in root.xpath('//*[({}) and count(span)=1]'.format(' or '.join(f'name()="{t}"' for t in parents))): if len(parent) == 1 and not parent.text and not parent[0].tail and not parent[0].get('id', None): # We have a block whose contents are entirely enclosed in a span = parent[0] diff --git a/src/calibre/ebooks/docx/container.py b/src/calibre/ebooks/docx/container.py index b7157d9749..28b7981412 100644 --- a/src/calibre/ebooks/docx/container.py +++ b/src/calibre/ebooks/docx/container.py @@ -137,7 +137,7 @@ class DOCX: try: raw = self.read('[Content_Types].xml') except KeyError: - raise InvalidDOCX('The file %s docx file has no [Content_Types].xml' % self.name) + raise InvalidDOCX(f'The file {self.name} docx file has no [Content_Types].xml') root = fromstring(raw) self.content_types = {} self.default_content_types = {} @@ -159,7 +159,7 @@ class DOCX: try: raw = self.read('_rels/.rels') except KeyError: - raise InvalidDOCX('The file %s docx file has no _rels/.rels' % self.name) + raise InvalidDOCX(f'The file {self.name} docx file has no _rels/.rels') root = fromstring(raw) self.relationships = {} self.relationships_rmap = {} @@ -177,7 +177,7 @@ class DOCX: if name is None: names = tuple(n for n in self.names if n == 'document.xml' or n.endswith('/document.xml')) if not names: - raise InvalidDOCX('The file %s docx file has no main document' % self.name) + raise InvalidDOCX(f'The file {self.name} docx file has no main document') name = names[0] return name diff --git a/src/calibre/ebooks/docx/fields.py b/src/calibre/ebooks/docx/fields.py index 6d931c3013..57eb184a51 100644 --- a/src/calibre/ebooks/docx/fields.py +++ b/src/calibre/ebooks/docx/fields.py @@ -145,11 +145,11 @@ class Fields: field_types = ('hyperlink', 'xe', 'index', 'ref', 'noteref') parsers = {x.upper():getattr(self, 'parse_'+x) for x in field_types} parsers.update({x:getattr(self, 'parse_'+x) for x in field_types}) - field_parsers = {f.upper():globals()['parse_%s' % f] for f in field_types} - field_parsers.update({f:globals()['parse_%s' % f] for f in field_types}) + field_parsers = {f.upper():globals()[f'parse_{f}'] for f in field_types} + field_parsers.update({f:globals()[f'parse_{f}'] for f in field_types}) for f in field_types: - setattr(self, '%s_fields' % f, []) + setattr(self, f'{f}_fields', []) unknown_fields = {'TOC', 'toc', 'PAGEREF', 'pageref'} # The TOC and PAGEREF fields are handled separately for field in self.fields: @@ -159,7 +159,7 @@ class Fields: if func is not None: func(field, field_parsers[field.name], log) elif field.name not in unknown_fields: - log.warn('Encountered unknown field: %s, ignoring it.' % field.name) + log.warn(f'Encountered unknown field: {field.name}, ignoring it.') unknown_fields.add(field.name) def get_runs(self, field): diff --git a/src/calibre/ebooks/docx/fonts.py b/src/calibre/ebooks/docx/fonts.py index 81b5cfc1f9..5c5a44730c 100644 --- a/src/calibre/ebooks/docx/fonts.py +++ b/src/calibre/ebooks/docx/fonts.py @@ -64,7 +64,7 @@ class Family: self.embedded = {} for x in ('Regular', 'Bold', 'Italic', 'BoldItalic'): - for y in XPath('./w:embed%s[@r:id]' % x)(elem): + for y in XPath(f'./w:embed{x}[@r:id]')(elem): rid = get(y, 'r:id') key = get(y, 'w:fontKey') subsetted = get(y, 'w:subsetted') in {'1', 'true', 'on'} @@ -166,14 +166,14 @@ class Fonts: os.mkdir(dest_dir) fname = self.write(name, dest_dir, docx, variant) if fname is not None: - d = {'font-family':'"%s"' % name.replace('"', ''), 'src': 'url("fonts/%s")' % fname} + d = {'font-family':'"{}"'.format(name.replace('"', '')), 'src': f'url("fonts/{fname}")'} if 'Bold' in variant: d['font-weight'] = 'bold' if 'Italic' in variant: d['font-style'] = 'italic' d = [f'{k}: {v}' for k, v in iteritems(d)] d = ';\n\t'.join(d) - defs.append('@font-face {\n\t%s\n}\n' % d) + defs.append(f'@font-face {{\n\t{d}\n}}\n') return '\n'.join(defs) def write(self, name, dest_dir, docx, variant): diff --git a/src/calibre/ebooks/docx/images.py b/src/calibre/ebooks/docx/images.py index 4e3c8de9ea..ff429252d4 100644 --- a/src/calibre/ebooks/docx/images.py +++ b/src/calibre/ebooks/docx/images.py @@ -50,9 +50,9 @@ def get_image_properties(parent, XPath, get): pass ans = {} if width is not None: - ans['width'] = '%.3gpt' % width + ans['width'] = f'{width:.3g}pt' if height is not None: - ans['height'] = '%.3gpt' % height + ans['height'] = f'{height:.3g}pt' alt = None title = None @@ -88,13 +88,13 @@ def get_image_properties(parent, XPath, get): def get_image_margins(elem): ans = {} for w, css in iteritems({'L':'left', 'T':'top', 'R':'right', 'B':'bottom'}): - val = elem.get('dist%s' % w, None) + val = elem.get(f'dist{w}', None) if val is not None: try: val = emu_to_pt(val) except (TypeError, ValueError): continue - ans['padding-%s' % css] = '%.3gpt' % val + ans[f'padding-{css}'] = f'{val:.3g}pt' return ans @@ -163,7 +163,7 @@ class Images: ext = what(None, raw) or base.rpartition('.')[-1] or 'jpeg' if ext == 'emf': # For an example, see: https://bugs.launchpad.net/bugs/1224849 - self.log('Found an EMF image: %s, trying to extract embedded raster image' % fname) + self.log(f'Found an EMF image: {fname}, trying to extract embedded raster image') from calibre.utils.wmf.emf import emf_unwrap try: raw = emf_unwrap(raw) @@ -247,9 +247,9 @@ class Images: try: src = self.generate_filename(rid, name) except LinkedImageNotFound as err: - self.log.warn('Linked image: %s not found, ignoring' % err.fname) + self.log.warn(f'Linked image: {err.fname} not found, ignoring') continue - img = IMG(src='images/%s' % src) + img = IMG(src=f'images/{src}') img.set('alt', alt or 'Image') if title: img.set('title', title) @@ -293,7 +293,7 @@ class Images: pass else: if pct > 0: - style['width'] = '%.3g%%' % pct + style['width'] = f'{pct:.3g}%' align = get(pict[0], 'o:hralign', 'center') if align in {'left', 'right'}: style['margin-left'] = '0' if align == 'left' else 'auto' @@ -308,10 +308,10 @@ class Images: try: src = self.generate_filename(rid) except LinkedImageNotFound as err: - self.log.warn('Linked image: %s not found, ignoring' % err.fname) + self.log.warn(f'Linked image: {err.fname} not found, ignoring') continue style = get(imagedata.getparent(), 'style') - img = IMG(src='images/%s' % src) + img = IMG(src=f'images/{src}') alt = get(imagedata, 'o:title') img.set('alt', alt or 'Image') if 'position:absolute' in style: diff --git a/src/calibre/ebooks/docx/index.py b/src/calibre/ebooks/docx/index.py index 39d9913989..49228120c0 100644 --- a/src/calibre/ebooks/docx/index.py +++ b/src/calibre/ebooks/docx/index.py @@ -69,7 +69,7 @@ def add_xe(xe, t, expand): p.append(r) t2 = r.makeelement(expand('w:t')) t2.set(expand('xml:space'), 'preserve') - t2.text = ' [%s]' % pt + t2.text = f' [{pt}]' r.append(t2) # put separate entries on separate lines run.insert(idx + 1, run.makeelement(expand('w:br'))) diff --git a/src/calibre/ebooks/docx/names.py b/src/calibre/ebooks/docx/names.py index 34c1d9951d..def15f0f7f 100644 --- a/src/calibre/ebooks/docx/names.py +++ b/src/calibre/ebooks/docx/names.py @@ -131,15 +131,15 @@ class DOCXNamespace: def ancestor(self, elem, name): try: - return self.XPath('ancestor::%s[1]' % name)(elem)[0] + return self.XPath(f'ancestor::{name}[1]')(elem)[0] except IndexError: return None def children(self, elem, *args): - return self.XPath('|'.join('child::%s' % a for a in args))(elem) + return self.XPath('|'.join(f'child::{a}' for a in args))(elem) def descendants(self, elem, *args): - return self.XPath('|'.join('descendant::%s' % a for a in args))(elem) + return self.XPath('|'.join(f'descendant::{a}' for a in args))(elem) def makeelement(self, root, tag, append=True, **attrs): ans = root.makeelement(self.expand(tag), **{self.expand(k, sep='_'):v for k, v in iteritems(attrs)}) diff --git a/src/calibre/ebooks/docx/numbering.py b/src/calibre/ebooks/docx/numbering.py index 37138e3a96..a99402648f 100644 --- a/src/calibre/ebooks/docx/numbering.py +++ b/src/calibre/ebooks/docx/numbering.py @@ -140,7 +140,7 @@ class Level: except Exception: fname = None else: - ans['list-style-image'] = 'url("images/%s")' % fname + ans['list-style-image'] = f'url("images/{fname}")' return ans def char_css(self): @@ -290,7 +290,7 @@ class Numbering: counter[ilvl] = self.starts[num_id][ilvl] seen_instances.add(num_id) p.tag = 'li' - p.set('value', '%s' % counter[ilvl]) + p.set('value', f'{counter[ilvl]}') p.set('list-lvl', str(ilvl)) p.set('list-id', num_id) if lvl.num_template is not None: @@ -381,8 +381,7 @@ class Numbering: obj = object_map[li] bs = styles.para_cache[obj] if i == 0: - wrap.set('style', 'display:table; padding-left:%s' % - bs.css.get('margin-left', '0')) + wrap.set('style', 'display:table; padding-left:{}'.format(bs.css.get('margin-left', '0'))) bs.css.pop('margin-left', None) for child in li: child.set('style', 'display:table-cell') diff --git a/src/calibre/ebooks/docx/styles.py b/src/calibre/ebooks/docx/styles.py index ed7aea361a..01557f201e 100644 --- a/src/calibre/ebooks/docx/styles.py +++ b/src/calibre/ebooks/docx/styles.py @@ -385,7 +385,7 @@ class Styles: fs = promote_most_common(block_styles, 'font_size', int(self.body_font_size[:2])) if fs is not None: - self.body_font_size = '%.3gpt' % fs + self.body_font_size = f'{fs:.3g}pt' color = promote_most_common(block_styles, 'color', self.body_color, inherit_means='currentColor') if color is not None: diff --git a/src/calibre/ebooks/docx/tables.py b/src/calibre/ebooks/docx/tables.py index 1a00af4456..c819ee6f3d 100644 --- a/src/calibre/ebooks/docx/tables.py +++ b/src/calibre/ebooks/docx/tables.py @@ -51,12 +51,12 @@ def read_cell_width(parent, dest, XPath, get): def read_padding(parent, dest, XPath, get): name = 'tblCellMar' if parent.tag.endswith('}tblPr') else 'tcMar' ans = {x:inherit for x in edges} - for mar in XPath('./w:%s' % name)(parent): + for mar in XPath(f'./w:{name}')(parent): for x in edges: - for edge in XPath('./w:%s' % x)(mar): + for edge in XPath(f'./w:{x}')(mar): ans[x] = _read_width(edge, get) for x in edges: - setattr(dest, 'cell_padding_%s' % x, ans[x]) + setattr(dest, f'cell_padding_{x}', ans[x]) def read_justification(parent, dest, XPath, get): @@ -135,7 +135,7 @@ def read_col_span(parent, dest, XPath, get): def read_merge(parent, dest, XPath, get): for x in ('hMerge', 'vMerge'): ans = inherit - for m in XPath('./w:%s' % x)(parent): + for m in XPath(f'./w:{x}')(parent): ans = get(m, 'w:val', 'continue') setattr(dest, x, ans) @@ -143,12 +143,12 @@ def read_merge(parent, dest, XPath, get): def read_band_size(parent, dest, XPath, get): for x in ('Col', 'Row'): ans = 1 - for y in XPath('./w:tblStyle%sBandSize' % x)(parent): + for y in XPath(f'./w:tblStyle{x}BandSize')(parent): try: ans = int(get(y, 'w:val')) except (TypeError, ValueError): continue - setattr(dest, '%s_band_size' % x.lower(), ans) + setattr(dest, f'{x.lower()}_band_size', ans) def read_look(parent, dest, XPath, get): @@ -201,9 +201,9 @@ class Style: c = {} for x in edges: border_to_css(x, self, c) - val = getattr(self, 'padding_%s' % x) + val = getattr(self, f'padding_{x}') if val is not inherit: - c['padding-%s' % x] = '%.3gpt' % val + c[f'padding-{x}'] = f'{val:.3g}pt' if self.is_bidi: for a in ('padding-%s', 'border-%s-style', 'border-%s-color', 'border-%s-width'): l, r = c.get(a % 'left'), c.get(a % 'right') @@ -227,7 +227,7 @@ class RowStyle(Style): for p in ('hidden', 'cantSplit'): setattr(self, p, binary_property(trPr, p, namespace.XPath, namespace.get)) for p in ('spacing', 'height'): - f = globals()['read_%s' % p] + f = globals()[f'read_{p}'] f(trPr, self, namespace.XPath, namespace.get) self._css = None @@ -263,7 +263,7 @@ class CellStyle(Style): setattr(self, p, inherit) else: for x in ('borders', 'shd', 'padding', 'cell_width', 'vertical_align', 'col_span', 'merge'): - f = globals()['read_%s' % x] + f = globals()[f'read_{x}'] f(tcPr, self, namespace.XPath, namespace.get) self.row_span = inherit self._css = None @@ -278,17 +278,17 @@ class CellStyle(Style): c['width'] = self.width c['vertical-align'] = 'top' if self.vertical_align is inherit else self.vertical_align for x in edges: - val = getattr(self, 'cell_padding_%s' % x) + val = getattr(self, f'cell_padding_{x}') if val not in (inherit, 'auto'): - c['padding-%s' % x] = val + c[f'padding-{x}'] = val elif val is inherit and x in {'left', 'right'}: - c['padding-%s' % x] = '%.3gpt' % (115/20) + c[f'padding-{x}'] = '%.3gpt' % (115/20) # In Word, tables are apparently rendered with some default top and # bottom padding irrespective of the cellMargin values. Simulate # that here. for x in ('top', 'bottom'): - if c.get('padding-%s' % x, '0pt') == '0pt': - c['padding-%s' % x] = '0.5ex' + if c.get(f'padding-{x}', '0pt') == '0pt': + c[f'padding-{x}'] = '0.5ex' c.update(self.convert_border()) return self._css @@ -311,7 +311,7 @@ class TableStyle(Style): self.overrides = inherit self.bidi = binary_property(tblPr, 'bidiVisual', namespace.XPath, namespace.get) for x in ('width', 'float', 'padding', 'shd', 'justification', 'spacing', 'indent', 'borders', 'band_size', 'look'): - f = globals()['read_%s' % x] + f = globals()[f'read_{x}'] f(tblPr, self, self.namespace.XPath, self.namespace.get) parent = tblPr.getparent() if self.namespace.is_tag(parent, 'w:style'): @@ -351,12 +351,12 @@ class TableStyle(Style): c['margin-left'] = self.indent if self.float is not inherit: for x in ('left', 'top', 'right', 'bottom'): - val = self.float.get('%sFromText' % x, 0) + val = self.float.get(f'{x}FromText', 0) try: val = '%.3gpt' % (int(val) / 20) except (ValueError, TypeError): val = '0' - c['margin-%s' % x] = val + c[f'margin-{x}'] = val if 'tblpXSpec' in self.float: c['float'] = 'right' if self.float['tblpXSpec'] in {'right', 'outside'} else 'left' else: @@ -516,7 +516,7 @@ class Table: cs.update(CellStyle(self.namespace, tcPr)) for x in edges: - p = 'cell_padding_%s' % x + p = f'cell_padding_{x}' val = getattr(cs, p) if val is inherit: setattr(cs, p, getattr(self.table_style, p)) diff --git a/src/calibre/ebooks/docx/to_html.py b/src/calibre/ebooks/docx/to_html.py index 4560006c30..eb10488529 100644 --- a/src/calibre/ebooks/docx/to_html.py +++ b/src/calibre/ebooks/docx/to_html.py @@ -151,7 +151,7 @@ class Convert: dl = DL(id=anchor) dl.set('class', 'footnote') self.body.append(dl) - dl.append(DT('[', A('←' + text, href='#back_%s' % anchor, title=text))) + dl.append(DT('[', A('←' + text, href=f'#back_{anchor}', title=text))) dl[-1][0].tail = ']' dl.append(DD()) paras = [] @@ -184,7 +184,7 @@ class Convert: if style.text_indent is inherit or (hasattr(style.text_indent, 'endswith') and style.text_indent.endswith('pt')): if style.text_indent is not inherit: indent = float(style.text_indent[:-2]) + indent - style.text_indent = '%.3gpt' % indent + style.text_indent = f'{indent:.3g}pt' parent.text = tabs[-1].tail or '' for i in tabs: parent.remove(i) @@ -236,7 +236,7 @@ class Convert: notes_header.tag = h.tag cls = h.get('class', None) if cls and cls != 'notes-header': - notes_header.set('class', '%s notes-header' % cls) + notes_header.set('class', f'{cls} notes-header') break self.fields.polish_markup(self.object_map) @@ -322,11 +322,11 @@ class Convert: try: seraw = self.docx.read(sename) except KeyError: - self.log.warn('Settings %s do not exist' % sename) + self.log.warn(f'Settings {sename} do not exist') except OSError as e: if e.errno != errno.ENOENT: raise - self.log.warn('Settings %s file missing' % sename) + self.log.warn(f'Settings {sename} file missing') else: self.settings(fromstring(seraw)) @@ -334,14 +334,14 @@ class Convert: try: foraw = self.docx.read(foname) except KeyError: - self.log.warn('Footnotes %s do not exist' % foname) + self.log.warn(f'Footnotes {foname} do not exist') else: forel = self.docx.get_relationships(foname) if enname is not None: try: enraw = self.docx.read(enname) except KeyError: - self.log.warn('Endnotes %s do not exist' % enname) + self.log.warn(f'Endnotes {enname} do not exist') else: enrel = self.docx.get_relationships(enname) footnotes(fromstring(foraw) if foraw else None, forel, fromstring(enraw) if enraw else None, enrel) @@ -351,7 +351,7 @@ class Convert: try: raw = self.docx.read(fname) except KeyError: - self.log.warn('Fonts table %s does not exist' % fname) + self.log.warn(f'Fonts table {fname} does not exist') else: fonts(fromstring(raw), embed_relationships, self.docx, self.dest_dir) @@ -359,7 +359,7 @@ class Convert: try: raw = self.docx.read(tname) except KeyError: - self.log.warn('Styles %s do not exist' % sname) + self.log.warn(f'Styles {sname} do not exist') else: self.theme(fromstring(raw)) @@ -368,7 +368,7 @@ class Convert: try: raw = self.docx.read(sname) except KeyError: - self.log.warn('Styles %s do not exist' % sname) + self.log.warn(f'Styles {sname} do not exist') else: self.styles(fromstring(raw), fonts, self.theme) styles_loaded = True @@ -379,7 +379,7 @@ class Convert: try: raw = self.docx.read(nname) except KeyError: - self.log.warn('Numbering styles %s do not exist' % nname) + self.log.warn(f'Numbering styles {nname} do not exist') else: numbering(fromstring(raw), self.styles, self.docx.get_relationships(nname)[0]) @@ -604,8 +604,7 @@ class Convert: if anchor and anchor in self.anchor_map: span.set('href', '#' + self.anchor_map[anchor]) continue - self.log.warn('Hyperlink with unknown target (rid=%s, anchor=%s), ignoring' % - (rid, anchor)) + self.log.warn(f'Hyperlink with unknown target (rid={rid}, anchor={anchor}), ignoring') # hrefs that point nowhere give epubcheck a hernia. The element # should be styled explicitly by Word anyway. # span.set('href', '#') @@ -630,7 +629,7 @@ class Convert: if anchor in self.anchor_map: span.set('href', '#' + self.anchor_map[anchor]) continue - self.log.warn('Hyperlink field with unknown anchor: %s' % anchor) + self.log.warn(f'Hyperlink field with unknown anchor: {anchor}') else: if url in self.anchor_map: span.set('href', '#' + self.anchor_map[url]) @@ -709,7 +708,7 @@ class Convert: elif self.namespace.is_tag(child, 'w:footnoteReference') or self.namespace.is_tag(child, 'w:endnoteReference'): anchor, name = self.footnotes.get_ref(child) if anchor and name: - l = A(name, id='back_%s' % anchor, href='#' + anchor, title=name) + l = A(name, id=f'back_{anchor}', href='#' + anchor, title=name) l.set('class', 'noteref') l.set('role', 'doc-noteref') text.add_elem(l) diff --git a/src/calibre/ebooks/docx/writer/container.py b/src/calibre/ebooks/docx/writer/container.py index 03f2ea90e7..52c5ef8bdb 100644 --- a/src/calibre/ebooks/docx/writer/container.py +++ b/src/calibre/ebooks/docx/writer/container.py @@ -160,8 +160,8 @@ class DOCX: namespaces = self.namespace.namespaces self.opts, self.log = opts, log self.document_relationships = DocumentRelationships(self.namespace) - self.font_table = etree.Element('{%s}fonts' % namespaces['w'], nsmap={k:namespaces[k] for k in 'wr'}) - self.numbering = etree.Element('{%s}numbering' % namespaces['w'], nsmap={k:namespaces[k] for k in 'wr'}) + self.font_table = etree.Element('{{{}}}fonts'.format(namespaces['w']), nsmap={k:namespaces[k] for k in 'wr'}) + self.numbering = etree.Element('{{{}}}numbering'.format(namespaces['w']), nsmap={k:namespaces[k] for k in 'wr'}) E = ElementMaker(namespace=namespaces['pr'], nsmap={None:namespaces['pr']}) self.embedded_fonts = E.Relationships() self.fonts = {} @@ -245,7 +245,7 @@ class DOCX: cp = E.coreProperties(E.revision('1'), E.lastModifiedBy('calibre')) ts = utcnow().isoformat(native_string_type('T')).rpartition('.')[0] + 'Z' for x in 'created modified'.split(): - x = cp.makeelement('{{{}}}{}'.format(namespaces['dcterms'], x), **{'{%s}type' % namespaces['xsi']:'dcterms:W3CDTF'}) + x = cp.makeelement('{{{}}}{}'.format(namespaces['dcterms'], x), **{'{{{}}}type'.format(namespaces['xsi']):'dcterms:W3CDTF'}) x.text = ts cp.append(x) self.mi = mi diff --git a/src/calibre/ebooks/docx/writer/fonts.py b/src/calibre/ebooks/docx/writer/fonts.py index 22e3989f2c..f6c2489312 100644 --- a/src/calibre/ebooks/docx/writer/fonts.py +++ b/src/calibre/ebooks/docx/writer/fonts.py @@ -72,5 +72,5 @@ class FontsManager: 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, - w_fontKey='{%s}' % key.urn.rpartition(':')[-1].upper(), + w_fontKey='{{{}}}'.format(key.urn.rpartition(':')[-1].upper()), w_subsetted='true' if self.opts.subset_embedded_fonts else 'false') diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index c0b53ad756..2fadea4027 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -259,7 +259,7 @@ class Block: makeelement(p, 'w:bookmarkEnd', w_id=bmark) def __repr__(self): - return 'Block(%r)' % self.runs + return f'Block({self.runs!r})' __str__ = __repr__ def is_empty(self): @@ -423,7 +423,7 @@ class Blocks: block.block_lang = None def __repr__(self): - return 'Block(%r)' % self.runs + return f'Block({self.runs!r})' class Convert: @@ -544,7 +544,7 @@ class Convert: else: if tagname == 'hr': for edge in 'right bottom left'.split(): - tag_style.set('border-%s-style' % edge, 'none') + tag_style.set(f'border-{edge}-style', 'none') self.add_block_tag(tagname, html_tag, tag_style, stylizer, float_spec=float_spec) for child in html_tag.iterchildren(): diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index 214c014efc..825819b935 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -74,7 +74,7 @@ class ImagesManager: try: fmt, width, height = identify(item.data) except Exception: - self.log.warning('Replacing corrupted image with blank: %s' % href) + self.log.warning(f'Replacing corrupted image with blank: {href}') item.data = I('blank.png', data=True, allow_user_override=False) fmt, width, height = identify(item.data) image_fname = 'media/' + self.create_filename(href, fmt) @@ -246,7 +246,7 @@ class ImagesManager: def write_cover_block(self, body, cover_image): makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces pbb = body[0].xpath('//*[local-name()="pageBreakBefore"]')[0] - pbb.set('{%s}val' % namespaces['w'], 'on') + pbb.set('{{{}}}val'.format(namespaces['w']), 'on') p = makeelement(body, 'w:p', append=False) body.insert(0, p) r = makeelement(p, 'w:r') diff --git a/src/calibre/ebooks/docx/writer/links.py b/src/calibre/ebooks/docx/writer/links.py index b34c2c215f..a6f9a0f17e 100644 --- a/src/calibre/ebooks/docx/writer/links.py +++ b/src/calibre/ebooks/docx/writer/links.py @@ -84,7 +84,7 @@ class LinksManager: if key in self.anchor_map: return self.anchor_map[key] if anchor == self.top_anchor: - name = ('Top of %s' % posixpath.basename(current_item.href)) + name = (f'Top of {posixpath.basename(current_item.href)}') self.document_hrefs.add(current_item.href) else: name = start_text(html_tag).strip() or anchor @@ -129,7 +129,7 @@ class LinksManager: bmark = self.anchor_map[(href, self.top_anchor)] return make_link(parent, anchor=bmark, tooltip=tooltip) else: - self.log.warn('Ignoring internal hyperlink with href (%s) pointing to unknown destination' % url) + self.log.warn(f'Ignoring internal hyperlink with href ({url}) pointing to unknown destination') if purl.scheme in {'http', 'https', 'ftp'}: if url not in self.external_links: self.external_links[url] = self.document_relationships.add_relationship(url, self.namespace.names['LINKS'], target_mode='External') @@ -164,7 +164,7 @@ class LinksManager: def serialize_toc(self, body, primary_heading_style): pbb = body[0].xpath('//*[local-name()="pageBreakBefore"]')[0] - pbb.set('{%s}val' % self.namespace.namespaces['w'], 'on') + pbb.set('{{{}}}val'.format(self.namespace.namespaces['w']), 'on') for block in reversed(self.toc): block.serialize(body, self.namespace.makeelement) title = __('Table of Contents') diff --git a/src/calibre/ebooks/docx/writer/styles.py b/src/calibre/ebooks/docx/writer/styles.py index 5b299c4076..c7cdc1cd18 100644 --- a/src/calibre/ebooks/docx/writer/styles.py +++ b/src/calibre/ebooks/docx/writer/styles.py @@ -135,10 +135,10 @@ class FloatSpec: bdr = self.makeelement(parent, 'w:pBdr') for edge in border_edges: padding = getattr(self, 'padding_' + edge) - width = getattr(self, 'border_%s_width' % edge) - bstyle = getattr(self, 'border_%s_style' % edge) + width = getattr(self, f'border_{edge}_width') + bstyle = getattr(self, f'border_{edge}_style') self.makeelement( - bdr, 'w:'+edge, w_space=str(padding), w_val=bstyle, w_sz=str(width), w_color=getattr(self, 'border_%s_color' % edge)) + bdr, 'w:'+edge, w_space=str(padding), w_val=bstyle, w_sz=str(width), w_color=getattr(self, f'border_{edge}_color')) class DOCXStyle: @@ -273,7 +273,7 @@ class TextStyle(DOCXStyle): self.padding = padding elif self.padding != padding: self.padding = ignore - val = css['border-%s-width' % edge] + val = css[f'border-{edge}-width'] if not isinstance(val, numbers.Number): val = {'thin':0.2, 'medium':1, 'thick':2}.get(val, 0) val = min(96, max(2, int(val * 8))) @@ -281,12 +281,12 @@ class TextStyle(DOCXStyle): self.border_width = val elif self.border_width != val: self.border_width = ignore - color = convert_color(css['border-%s-color' % edge]) + color = convert_color(css[f'border-{edge}-color']) if self.border_color is None: self.border_color = color elif self.border_color != color: self.border_color = ignore - style = LINE_STYLES.get(css['border-%s-style' % edge].lower(), 'none') + style = LINE_STYLES.get(css[f'border-{edge}-style'].lower(), 'none') if self.border_style is None: self.border_style = style elif self.border_style != style: @@ -477,11 +477,11 @@ def read_css_block_borders(self, css, store_css_style=False): setattr(self, 'padding_' + edge, 0) setattr(self, 'margin_' + edge, 0) setattr(self, 'css_margin_' + edge, '') - setattr(self, 'border_%s_width' % edge, 2) - setattr(self, 'border_%s_color' % edge, None) - setattr(self, 'border_%s_style' % edge, 'none') + setattr(self, f'border_{edge}_width', 2) + setattr(self, f'border_{edge}_color', None) + setattr(self, f'border_{edge}_style', 'none') if store_css_style: - setattr(self, 'border_%s_css_style' % edge, 'none') + setattr(self, f'border_{edge}_css_style', 'none') else: # In DOCX padding can only be a positive integer try: @@ -494,15 +494,15 @@ def read_css_block_borders(self, css, store_css_style=False): except ValueError: setattr(self, 'margin_' + edge, 0) # e.g.: margin: auto setattr(self, 'css_margin_' + edge, css._style.get('margin-' + edge, '')) - val = css['border-%s-width' % edge] + val = css[f'border-{edge}-width'] if not isinstance(val, numbers.Number): val = {'thin':0.2, 'medium':1, 'thick':2}.get(val, 0) val = min(96, max(2, int(val * 8))) - setattr(self, 'border_%s_width' % edge, val) - setattr(self, 'border_%s_color' % edge, convert_color(css['border-%s-color' % edge]) or 'auto') - setattr(self, 'border_%s_style' % edge, LINE_STYLES.get(css['border-%s-style' % edge].lower(), 'none')) + setattr(self, f'border_{edge}_width', val) + setattr(self, f'border_{edge}_color', convert_color(css[f'border-{edge}-color']) or 'auto') + setattr(self, f'border_{edge}_style', LINE_STYLES.get(css[f'border-{edge}-style'].lower(), 'none')) if store_css_style: - setattr(self, 'border_%s_css_style' % edge, css['border-%s-style' % edge].lower()) + setattr(self, f'border_{edge}_css_style', css[f'border-{edge}-style'].lower()) class BlockStyle(DOCXStyle): @@ -518,8 +518,8 @@ class BlockStyle(DOCXStyle): read_css_block_borders(self, css) if is_table_cell: for edge in border_edges: - setattr(self, 'border_%s_style' % edge, 'none') - setattr(self, 'border_%s_width' % edge, 0) + setattr(self, f'border_{edge}_style', 'none') + setattr(self, f'border_{edge}_width', 0) setattr(self, 'padding_' + edge, 0) setattr(self, 'margin_' + edge, 0) if css is None: @@ -565,14 +565,14 @@ class BlockStyle(DOCXStyle): padding = getattr(self, 'padding_' + edge) if (self is normal_style and padding > 0) or (padding != getattr(normal_style, 'padding_' + edge)): e.set(w('space'), str(padding)) - width = getattr(self, 'border_%s_width' % edge) - bstyle = getattr(self, 'border_%s_style' % edge) + width = getattr(self, f'border_{edge}_width') + bstyle = getattr(self, f'border_{edge}_style') if (self is normal_style and width > 0 and bstyle != 'none' - ) or width != getattr(normal_style, 'border_%s_width' % edge - ) or bstyle != getattr(normal_style, 'border_%s_style' % edge): + ) or width != getattr(normal_style, f'border_{edge}_width' + ) or bstyle != getattr(normal_style, f'border_{edge}_style'): e.set(w('val'), bstyle) e.set(w('sz'), str(width)) - e.set(w('color'), getattr(self, 'border_%s_color' % edge)) + e.set(w('color'), getattr(self, f'border_{edge}_color')) if e.attrib: bdr.append(e) return bdr diff --git a/src/calibre/ebooks/docx/writer/tables.py b/src/calibre/ebooks/docx/writer/tables.py index 7f2e83b28c..6427af7760 100644 --- a/src/calibre/ebooks/docx/writer/tables.py +++ b/src/calibre/ebooks/docx/writer/tables.py @@ -46,10 +46,10 @@ def read_css_block_borders(self, css): rcbb(obj, css, store_css_style=True) for edge in border_edges: setattr(self, 'border_' + edge, Border( - getattr(obj, 'border_%s_css_style' % edge), - getattr(obj, 'border_%s_style' % edge), - getattr(obj, 'border_%s_width' % edge), - getattr(obj, 'border_%s_color' % edge), + getattr(obj, f'border_{edge}_css_style'), + getattr(obj, f'border_{edge}_style'), + getattr(obj, f'border_{edge}_width'), + getattr(obj, f'border_{edge}_color'), self.BLEVEL )) setattr(self, 'padding_' + edge, getattr(obj, 'padding_' + edge)) diff --git a/src/calibre/ebooks/epub/__init__.py b/src/calibre/ebooks/epub/__init__.py index a8cd6cec96..813390d5fc 100644 --- a/src/calibre/ebooks/epub/__init__.py +++ b/src/calibre/ebooks/epub/__init__.py @@ -17,15 +17,15 @@ def rules(stylesheets): def simple_container_xml(opf_path, extra_entries=''): - return '''\ + return f'''\ - + {extra_entries} - '''.format(opf_path, extra_entries=extra_entries) + ''' def initialize_container(path_to_container, opf_name='metadata.opf', @@ -35,8 +35,7 @@ def initialize_container(path_to_container, opf_name='metadata.opf', ''' rootfiles = '' for path, mimetype, _ in extra_entries: - rootfiles += ''.format( - path, mimetype) + rootfiles += f'' CONTAINER = simple_container_xml(opf_name, rootfiles).encode('utf-8') zf = ZipFile(path_to_container, 'w') zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED) diff --git a/src/calibre/ebooks/epub/cfi/parse.py b/src/calibre/ebooks/epub/cfi/parse.py index c43bb3775b..a5e97e2174 100644 --- a/src/calibre/ebooks/epub/cfi/parse.py +++ b/src/calibre/ebooks/epub/cfi/parse.py @@ -26,29 +26,29 @@ class Parser: integer = r'(?:[1-9][0-9]*)|0' # No leading zeros, except for numbers in (0, 1) and no trailing zeros for the fractional part frac = r'\.[0-9]{1,}' - number = r'(?:[1-9][0-9]*(?:{0})?)|(?:0{0})|(?:0)'.format(frac) + number = rf'(?:[1-9][0-9]*(?:{frac})?)|(?:0{frac})|(?:0)' def c(x): return regex.compile(x, flags=regex.VERSION1) # A step of the form /integer - self.step_pat = c(r'/(%s)' % integer) + self.step_pat = c(rf'/({integer})') # An id assertion of the form [characters] - self.id_assertion_pat = c(r'\[(%s)\]' % chars) + self.id_assertion_pat = c(rf'\[({chars})\]') # A text offset of the form :integer - self.text_offset_pat = c(r':(%s)' % integer) + self.text_offset_pat = c(rf':({integer})') # A temporal offset of the form ~number - self.temporal_offset_pat = c(r'~(%s)' % number) + self.temporal_offset_pat = c(rf'~({number})') # A spatial offset of the form @number:number - self.spatial_offset_pat = c(r'@({0}):({0})'.format(number)) + self.spatial_offset_pat = c(rf'@({number}):({number})') # A spatio-temporal offset of the form ~number@number:number - self.st_offset_pat = c(r'~({0})@({0}):({0})'.format(number)) + self.st_offset_pat = c(rf'~({number})@({number}):({number})') # Text assertion patterns - self.ta1_pat = c(r'({0})(?:,({0})){{0,1}}'.format(chars)) - self.ta2_pat = c(r',(%s)' % chars) + self.ta1_pat = c(rf'({chars})(?:,({chars})){{0,1}}') + self.ta2_pat = c(rf',({chars})') self.parameters_pat = c(fr'(?:;({chars_no_space})=((?:{chars},?)+))+') - self.csv_pat = c(r'(?:(%s),?)+' % chars) + self.csv_pat = c(rf'(?:({chars}),?)+') # Unescape characters unescape_pat = c(fr'{escaped_char[:2]}({escaped_char[2:]})') @@ -194,7 +194,7 @@ def cfi_sort_key(cfi, only_path=True): return (), (0, (0, 0), 0) if not pcfi: import sys - print('Failed to parse CFI: %r' % cfi, file=sys.stderr) + print(f'Failed to parse CFI: {cfi!r}', file=sys.stderr) return (), (0, (0, 0), 0) steps = get_steps(pcfi) step_nums = tuple(s.get('num', 0) for s in steps) @@ -214,7 +214,7 @@ def decode_cfi(root, cfi): return if not pcfi: import sys - print('Failed to parse CFI: %r' % pcfi, file=sys.stderr) + print(f'Failed to parse CFI: {pcfi!r}', file=sys.stderr) return steps = get_steps(pcfi) ans = root @@ -222,7 +222,7 @@ def decode_cfi(root, cfi): num = step.get('num', 0) node_id = step.get('id') try: - match = ans.xpath('descendant::*[@id="%s"]' % node_id) + match = ans.xpath(f'descendant::*[@id="{node_id}"]') except XPathEvalError: match = () if match: diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index a24fd2d72c..4f9f883777 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -144,10 +144,10 @@ class FB2MLizer: author_middle = ' '.join(author_parts[1:-1]) author_last = author_parts[-1] metadata['author'] += '' - metadata['author'] += '%s' % prepare_string_for_xml(author_first) + metadata['author'] += f'{prepare_string_for_xml(author_first)}' if author_middle: - metadata['author'] += '%s' % prepare_string_for_xml(author_middle) - metadata['author'] += '%s' % prepare_string_for_xml(author_last) + metadata['author'] += f'{prepare_string_for_xml(author_middle)}' + metadata['author'] += f'{prepare_string_for_xml(author_last)}' metadata['author'] += '' if not metadata['author']: metadata['author'] = '' @@ -156,14 +156,14 @@ class FB2MLizer: tags = list(map(str, self.oeb_book.metadata.subject)) if tags: tags = ', '.join(prepare_string_for_xml(x) for x in tags) - metadata['keywords'] = '%s'%tags + metadata['keywords'] = f'{tags}' metadata['sequence'] = '' if self.oeb_book.metadata.series: index = '1' if self.oeb_book.metadata.series_index: index = self.oeb_book.metadata.series_index[0] - metadata['sequence'] = ''.format(prepare_string_for_xml('%s' % self.oeb_book.metadata.series[0]), index) + metadata['sequence'] = ''.format(prepare_string_for_xml(f'{self.oeb_book.metadata.series[0]}'), index) year = publisher = isbn = '' identifiers = self.oeb_book.metadata['identifier'] @@ -180,18 +180,18 @@ class FB2MLizer: except IndexError: pass else: - year = '%s' % prepare_string_for_xml(date.value.partition('-')[0]) + year = '{}'.format(prepare_string_for_xml(date.value.partition('-')[0])) try: publisher = self.oeb_book.metadata['publisher'][0] except IndexError: pass else: - publisher = '%s' % prepare_string_for_xml(publisher.value) + publisher = f'{prepare_string_for_xml(publisher.value)}' for x in identifiers: if x.get(OPF('scheme'), None).lower() == 'isbn': - isbn = '%s' % prepare_string_for_xml(x.value) + isbn = f'{prepare_string_for_xml(x.value)}' metadata['year'], metadata['isbn'], metadata['publisher'] = year, isbn, publisher for key, value in metadata.items(): @@ -269,8 +269,8 @@ class FB2MLizer: if cover_href: # Only write the image tag if it is in the manifest. if cover_href in self.oeb_book.manifest.hrefs and cover_href not in self.image_hrefs: - self.image_hrefs[cover_href] = 'img_%s' % len(self.image_hrefs) - return '' % self.image_hrefs[cover_href] + self.image_hrefs[cover_href] = f'img_{len(self.image_hrefs)}' + return f'' return '' @@ -285,7 +285,7 @@ class FB2MLizer: self.section_level += 1 for item in self.oeb_book.spine: - self.log.debug('Converting %s to FictionBook2 XML' % item.href) + self.log.debug(f'Converting {item.href} to FictionBook2 XML') stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts, self.opts.output_profile) # Start a
if we must sectionize each file or if the TOC references this page @@ -334,8 +334,8 @@ class FB2MLizer: data = '\n'.join(raw_data[i:i+step] for i in range(0, len(raw_data), step)) images.append(f'{data}') except Exception as e: - self.log.error('Error: Could not include file %s because ' - '%s.' % (item.href, e)) + self.log.error(f'Error: Could not include file {item.href} because ' + f'{e}.') return '\n'.join(images) def create_flat_toc(self, nodes, level): @@ -365,13 +365,13 @@ class FB2MLizer: closed_tags = [] tags.reverse() for t in tags: - text.append('' % t) + text.append(f'') closed_tags.append(t) if t == 'p': break closed_tags.reverse() for t in closed_tags: - text.append('<%s>' % t) + text.append(f'<{t}>') else: text.append('

') added_p = True @@ -386,7 +386,7 @@ class FB2MLizer: p_out, p_tags = self.ensure_p() s_out += p_out s_tags += p_tags - s_out.append('<%s>' % tag) + s_out.append(f'<{tag}>') s_tags.append(tag) return s_out, s_tags @@ -476,13 +476,13 @@ class FB2MLizer: ihref = urlnormalize(page.abshref(elem_tree.attrib['src'])) if ihref in self.oeb_book.manifest.hrefs: if ihref not in self.image_hrefs: - self.image_hrefs[ihref] = 'img_%s' % len(self.image_hrefs) + self.image_hrefs[ihref] = f'img_{len(self.image_hrefs)}' p_txt, p_tag = self.ensure_p() fb2_out += p_txt tags += p_tag - fb2_out.append('' % self.image_hrefs[ihref]) + fb2_out.append(f'') else: - self.log.warn('Ignoring image not in manifest: %s' % ihref) + self.log.warn(f'Ignoring image not in manifest: {ihref}') if tag in ('br', 'hr') or ems >= 1: if ems < 1: multiplier = 1 @@ -493,14 +493,14 @@ class FB2MLizer: open_tags = tag_stack+tags open_tags.reverse() for t in open_tags: - fb2_out.append('' % t) + fb2_out.append(f'') closed_tags.append(t) if t == 'p': break fb2_out.append('' * multiplier) closed_tags.reverse() for t in closed_tags: - fb2_out.append('<%s>' % t) + fb2_out.append(f'<{t}>') else: fb2_out.append('' * multiplier) if tag in ('div', 'li', 'p'): @@ -514,7 +514,7 @@ class FB2MLizer: p_txt, p_tag = self.ensure_p() fb2_out += p_txt tags += p_tag - fb2_out.append('' % urlnormalize(elem_tree.attrib['href'])) + fb2_out.append(''.format(urlnormalize(elem_tree.attrib['href']))) tags.append('a') if tag == 'b' or style['font-weight'] in ('bold', 'bolder'): s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags) @@ -566,7 +566,7 @@ class FB2MLizer: def close_tags(self, tags): text = [] for tag in tags: - text.append('' % tag) + text.append(f'') if tag == 'p': self.in_p = False diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 54f8b6e019..9f581808ad 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -66,7 +66,7 @@ class Link: return self.path == getattr(other, 'path', other) def __str__(self): - return 'Link: %s --> %s'%(self.url, self.path) + return f'Link: {self.url} --> {self.path}' class IgnoreFile(Exception): @@ -131,7 +131,7 @@ class HTMLFile: if not src: if level == 0: - raise ValueError('The file %s is empty'%self.path) + raise ValueError(f'The file {self.path} is empty') self.is_binary = True if not self.is_binary: @@ -271,7 +271,7 @@ def traverse(path_to_html_file, max_levels=sys.maxsize, verbose=0, encoding=None continue seen.add(nf.path) if nf.is_binary: - raise IgnoreFile('%s is a binary file'%nf.path, -1) + raise IgnoreFile(f'{nf.path} is a binary file', -1) nl.append(nf) flat.append(nf) except IgnoreFile as err: diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index 23710d0fb1..dd9e9891b8 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -55,11 +55,10 @@ class OEB2HTML: def mlize_spine(self, oeb_book): output = [ - '%s' % ( - prepare_string_for_xml(self.book_title)) + f'{prepare_string_for_xml(self.book_title)}' ] for item in oeb_book.spine: - self.log.debug('Converting %s to HTML...' % item.href) + self.log.debug(f'Converting {item.href} to HTML...') self.rewrite_ids(item.data, item) rewrite_links(item.data, partial(self.rewrite_link, page=item)) stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) @@ -73,9 +72,9 @@ class OEB2HTML: def get_link_id(self, href, id=''): if id: - href += '#%s' % id + href += f'#{id}' if href not in self.links: - self.links[href] = '#calibre_link-%s' % len(self.links.keys()) + self.links[href] = f'#calibre_link-{len(self.links.keys())}' return self.links[href] def map_resources(self, oeb_book): @@ -111,7 +110,7 @@ class OEB2HTML: return url abs_url = page.abshref(urlnormalize(url)) if abs_url in self.images: - return 'images/%s' % self.images[abs_url] + return f'images/{self.images[abs_url]}' if abs_url in self.links: return self.links[abs_url] return url @@ -226,7 +225,7 @@ class OEB2HTMLNoCSSizer(OEB2HTML): tags.reverse() for t in tags: if t not in SELF_CLOSING_TAGS: - text.append('' % t) + text.append(f'') # Add the text that is outside of the tag. if hasattr(elem, 'tail') and elem.tail: @@ -262,14 +261,14 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): tag = barename(elem.tag) attribs = elem.attrib - style_a = '%s' % style + style_a = f'{style}' style_a = style_a if style_a else '' if tag == 'body': # Change the body to a div so we can merge multiple files. tag = 'div' # Add page-break-brefore: always because renders typically treat a new file (we're merging files) # as a page break and remove all other page break types that might be set. - style_a = 'page-break-before: always; %s' % re.sub(r'page-break-[^:]+:[^;]+;?', '', style_a) + style_a = 'page-break-before: always; {}'.format(re.sub(r'page-break-[^:]+:[^;]+;?', '', style_a)) # Remove unnecessary spaces. style_a = re.sub(r'\s{2,}', ' ', style_a).strip() tags.append(tag) @@ -289,7 +288,7 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): # Turn style into strings for putting in the tag. style_t = '' if style_a: - style_t = ' style="%s"' % style_a.replace('"', "'") + style_t = ' style="{}"'.format(style_a.replace('"', "'")) # Write the tag. text.append(f'<{tag}{at}{style_t}') @@ -310,7 +309,7 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): tags.reverse() for t in tags: if t not in SELF_CLOSING_TAGS: - text.append('' % t) + text.append(f'') # Add the text that is outside of the tag. if hasattr(elem, 'tail') and elem.tail: @@ -329,7 +328,7 @@ class OEB2HTMLClassCSSizer(OEB2HTML): def mlize_spine(self, oeb_book): output = [] for item in oeb_book.spine: - self.log.debug('Converting %s to HTML...' % item.href) + self.log.debug(f'Converting {item.href} to HTML...') self.rewrite_ids(item.data, item) rewrite_links(item.data, partial(self.rewrite_link, page=item)) stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) @@ -339,7 +338,7 @@ class OEB2HTMLClassCSSizer(OEB2HTML): css = '' else: css = '' - title = '%s' % prepare_string_for_xml(self.book_title) + title = f'{prepare_string_for_xml(self.book_title)}' output = [''] + \ [css] + [title, ''] + output + [''] return ''.join(output) @@ -398,7 +397,7 @@ class OEB2HTMLClassCSSizer(OEB2HTML): tags.reverse() for t in tags: if t not in SELF_CLOSING_TAGS: - text.append('' % t) + text.append(f'') # Add the text that is outside of the tag. if hasattr(elem, 'tail') and elem.tail: diff --git a/src/calibre/ebooks/lit/mssha1.py b/src/calibre/ebooks/lit/mssha1.py index 2a5eb6a721..5de7d30385 100644 --- a/src/calibre/ebooks/lit/mssha1.py +++ b/src/calibre/ebooks/lit/mssha1.py @@ -280,7 +280,7 @@ class mssha1: used to exchange the value safely in email or other non- binary environments. ''' - return ''.join(['%02x' % c for c in bytearray(self.digest())]) + return ''.join([f'{c:02x}' for c in bytearray(self.digest())]) def copy(self): '''Return a clone object. @@ -323,7 +323,7 @@ if __name__ == '__main__': import sys file = None if len(sys.argv) > 2: - print('usage: %s [FILE]' % sys.argv[0]) + print(f'usage: {sys.argv[0]} [FILE]') return elif len(sys.argv) < 2: file = sys.stdin diff --git a/src/calibre/ebooks/lit/reader.py b/src/calibre/ebooks/lit/reader.py index ea9f86588f..d26547471c 100644 --- a/src/calibre/ebooks/lit/reader.py +++ b/src/calibre/ebooks/lit/reader.py @@ -85,7 +85,7 @@ def encint(byts, remaining): def msguid(bytes): values = struct.unpack('>= 1 elsize += 1 if (mask <= 1) or (mask == 0x40): - raise LitError('Invalid UTF8 character: %s' % repr(bytes[pos])) + raise LitError(f'Invalid UTF8 character: {bytes[pos]!r}') else: elsize = 1 if elsize > 1: if elsize + pos > len(bytes): - raise LitError('Invalid UTF8 character: %s' % repr(bytes[pos])) + raise LitError(f'Invalid UTF8 character: {bytes[pos]!r}') c &= (mask - 1) for i in range(1, elsize): b = ord(bytes[pos+i:pos+i+1]) if (b & 0xC0) != 0x80: raise LitError( - 'Invalid UTF8 character: %s' % repr(bytes[pos:pos+i])) + f'Invalid UTF8 character: {bytes[pos:pos+i]!r}') c = (c << 6) | (b & 0x3F) return codepoint_to_chr(c), pos+elsize @@ -253,7 +253,7 @@ class UnBinary: errors += 1 tag_name = '?'+codepoint_to_chr(tag)+'?' current_map = self.tag_to_attr_map[tag] - print('WARNING: tag %s unknown' % codepoint_to_chr(tag)) + print(f'WARNING: tag {codepoint_to_chr(tag)} unknown') buf.write(encode(tag_name)) elif flags & FLAG_CLOSING: if depth == 0: @@ -384,7 +384,7 @@ class UnBinary: if frag: path = '#'.join((path, frag)) path = urlnormalize(path) - buf.write(encode('"%s"' % path)) + buf.write(encode(f'"{path}"')) state = 'get attr' @@ -578,7 +578,7 @@ class LitFile: for i in range(self.num_pieces): piece = src[i * self.PIECE_SIZE:(i + 1) * self.PIECE_SIZE] if u32(piece[4:]) != 0 or u32(piece[12:]) != 0: - raise LitError('Piece %s has 64bit value' % repr(piece)) + raise LitError(f'Piece {piece!r} has 64bit value') offset, size = u32(piece), int32(piece[8:]) piece = self.read_raw(offset, size) if i == 0: @@ -790,7 +790,7 @@ class LitFile: content = self.decompress(content, control, reset_table) control = control[csize:] else: - raise LitError('Unrecognized transform: %s.' % repr(guid)) + raise LitError(f'Unrecognized transform: {guid!r}.') transform = transform[16:] return content @@ -917,7 +917,7 @@ class LitContainer: unbin = UnBinary(raw, name, manifest, HTML_MAP, atoms) content = HTML_DECL + unbin.unicode_representation tags = ('personname', 'place', 'city', 'country-region') - pat = r'(?i)'%('|'.join(tags)) + pat = r'(?i)'.format('|'.join(tags)) content = re.sub(pat, '', content) content = re.sub(r'<(/{0,1})form>', r'<\1div>', content) else: diff --git a/src/calibre/ebooks/lit/writer.py b/src/calibre/ebooks/lit/writer.py index 29644d14b3..4057b72db9 100644 --- a/src/calibre/ebooks/lit/writer.py +++ b/src/calibre/ebooks/lit/writer.py @@ -278,8 +278,8 @@ class ReBinary: def build_ahc(self): if len(self.anchors) > 6: - self.logger.warn('More than six anchors in file %r. ' - 'Some links may not work properly.' % self.item.href) + self.logger.warn(f'More than six anchors in file {self.item.href!r}. ' + 'Some links may not work properly.') data = io.BytesIO() data.write(codepoint_to_chr(len(self.anchors)).encode('utf-8')) for anchor, offset in self.anchors: @@ -472,8 +472,8 @@ class LitWriter: self._add_folder('/data') for item in self._oeb.manifest.values(): if item.media_type not in LIT_MIMES: - self._logger.warn('File %r of unknown media-type %r ' - 'excluded from output.' % (item.href, item.media_type)) + self._logger.warn(f'File {item.href!r} of unknown media-type {item.media_type!r} ' + 'excluded from output.') continue name = '/data/' + item.id data = item.data @@ -571,7 +571,7 @@ class LitWriter: _, meta = self._oeb.to_opf1()[OPF_MIME] meta.attrib['ms--minimum_level'] = '0' meta.attrib['ms--attr5'] = '1' - meta.attrib['ms--guid'] = '{%s}' % native_string_type(uuid.uuid4()).upper() + meta.attrib['ms--guid'] = f'{{{native_string_type(uuid.uuid4()).upper()}}}' rebin = ReBinary(meta, None, self._oeb, self.opts, map=OPF_MAP) meta = rebin.content self._meta = meta diff --git a/src/calibre/ebooks/lrf/__init__.py b/src/calibre/ebooks/lrf/__init__.py index 865b6a31fc..d53ead8ef4 100644 --- a/src/calibre/ebooks/lrf/__init__.py +++ b/src/calibre/ebooks/lrf/__init__.py @@ -44,17 +44,17 @@ def find_custom_fonts(options, logger): f = family(options.serif_family) fonts['serif'] = font_scanner.legacy_fonts_for_family(f) if not fonts['serif']: - logger.warn('Unable to find serif family %s'%f) + logger.warn(f'Unable to find serif family {f}') if options.sans_family: f = family(options.sans_family) fonts['sans'] = font_scanner.legacy_fonts_for_family(f) if not fonts['sans']: - logger.warn('Unable to find sans family %s'%f) + logger.warn(f'Unable to find sans family {f}') if options.mono_family: f = family(options.mono_family) fonts['mono'] = font_scanner.legacy_fonts_for_family(f) if not fonts['mono']: - logger.warn('Unable to find mono family %s'%f) + logger.warn(f'Unable to find mono family {f}') return fonts diff --git a/src/calibre/ebooks/lrf/fonts.py b/src/calibre/ebooks/lrf/fonts.py index 9cdda9b282..d37b3ab83e 100644 --- a/src/calibre/ebooks/lrf/fonts.py +++ b/src/calibre/ebooks/lrf/fonts.py @@ -27,6 +27,6 @@ def get_font(name, size, encoding='unic'): ''' from calibre.utils.resources import get_path as P if name in LIBERATION_FONT_MAP: - return ImageFont.truetype(P('fonts/liberation/%s.ttf' % LIBERATION_FONT_MAP[name]), size, encoding=encoding) + return ImageFont.truetype(P(f'fonts/liberation/{LIBERATION_FONT_MAP[name]}.ttf'), size, encoding=encoding) elif name in FONT_FILE_MAP: return ImageFont.truetype(FONT_FILE_MAP[name], size, encoding=encoding) diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index a1e7b8731c..d8576b26b2 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -93,8 +93,8 @@ def strip_style_comments(match): def tag_regex(tagname): '''Return non-grouping regular expressions that match the opening and closing tags for tagname''' - return dict(open=r'(?:<\s*%(t)s\s+[^<>]*?>|<\s*%(t)s\s*>)'%dict(t=tagname), - close=r''%dict(t=tagname)) + return dict(open=r'(?:<\s*{t}\s+[^<>]*?>|<\s*{t}\s*>)'.format(**dict(t=tagname)), + close=r''.format(**dict(t=tagname))) class HTMLConverter: @@ -112,7 +112,7 @@ class HTMLConverter: lambda match: match.group().replace('', '')), # remove

tags from within tags (re.compile(r'<\s*a\s+[^<>]*href\s*=[^<>]*>(.*?)<\s*/\s*a\s*>', re.DOTALL|re.IGNORECASE), - lambda match: re.compile(r'%(open)s|%(close)s'%tag_regex('p'), re.IGNORECASE).sub('', match.group())), + lambda match: re.compile(r'{open}|{close}'.format(**tag_regex('p')), re.IGNORECASE).sub('', match.group())), # Replace common line break patterns with line breaks (re.compile(r'

( |\s)*

', re.IGNORECASE), lambda m: '
'), @@ -132,7 +132,7 @@ class HTMLConverter: # BeautifulSoup treats self closing
tags as open
tags (re.compile(r'(?i)<\s*div([^>]*)/\s*>'), - lambda match: '
'%match.group(1)) + lambda match: f'
') ] # Fix Baen markup @@ -167,13 +167,13 @@ class HTMLConverter: lambda match : ' '), # Create header tags (re.compile(r'<]*?id=BookTitle[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?', re.IGNORECASE), - lambda match : '

%s

'%(match.group(2) if match.group(2) else 'center', match.group(3))), + lambda match : '

{}

'.format(match.group(2) if match.group(2) else 'center', match.group(3))), (re.compile(r'<]*?id=BookAuthor[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?', re.IGNORECASE), - lambda match : '

%s

'%(match.group(2) if match.group(2) else 'center', match.group(3))), + lambda match : '

{}

'.format(match.group(2) if match.group(2) else 'center', match.group(3))), (re.compile(r'<]*?id=title[^><]*?>(.*?)', re.IGNORECASE|re.DOTALL), - lambda match : '

%s

'%(match.group(1),)), + lambda match : f'

{match.group(1)}

'), (re.compile(r'<]*?id=subtitle[^><]*?>(.*?)', re.IGNORECASE|re.DOTALL), - lambda match : '

%s

'%(match.group(1),)), + lambda match : f'

{match.group(1)}

'), # Blank lines (re.compile(r'<]*?>( ){4}', re.IGNORECASE), lambda match : '

'), @@ -614,7 +614,7 @@ class HTMLConverter: hasattr(target.parent, 'objId'): self.book.addTocEntry(ascii_text, tb) else: - self.log.debug('Cannot add link %s to TOC'%ascii_text) + self.log.debug(f'Cannot add link {ascii_text} to TOC') def get_target_block(fragment, targets): '''Return the correct block for the
element''' @@ -938,7 +938,7 @@ class HTMLConverter: try: im = PILImage.open(path) except OSError as err: - self.log.warning('Unable to process image: %s\n%s'%(original_path, err)) + self.log.warning(f'Unable to process image: {original_path}\n{err}') return encoding = detect_encoding(im) @@ -1017,8 +1017,7 @@ class HTMLConverter: try: self.images[path] = ImageStream(path, encoding=encoding) except LrsError as err: - self.log.warning(('Could not process image: %s\n%s')%( - original_path, err)) + self.log.warning(f'Could not process image: {original_path}\n{err}') return im = Image(self.images[path], x0=0, y0=0, x1=width, y1=height, @@ -1080,7 +1079,7 @@ class HTMLConverter: if number_of_paragraphs > 2: self.end_page() - self.log.debug('Forcing page break at %s'%tagname) + self.log.debug(f'Forcing page break at {tagname}') return end_page def block_properties(self, tag_css): @@ -1470,7 +1469,7 @@ class HTMLConverter: (self.chapter_attr[1].lower() == 'none' or (tag.has_attr(self.chapter_attr[1]) and self.chapter_attr[2].match(tag[self.chapter_attr[1]])))): - self.log.debug('Detected chapter %s'%tagname) + self.log.debug(f'Detected chapter {tagname}') self.end_page() self.page_break_found = True @@ -1530,7 +1529,7 @@ class HTMLConverter: elif not urlparse(tag['src'])[0]: self.log.warn('Could not find image: '+tag['src']) else: - self.log.debug('Failed to process: %s'%str(tag)) + self.log.debug(f'Failed to process: {tag!s}') elif tagname in ['style', 'link']: ncss, npcss = {}, {} if tagname == 'style': @@ -1682,7 +1681,7 @@ class HTMLConverter: if not self.disable_chapter_detection and tagname.startswith('h'): if self.chapter_regex.search(src): - self.log.debug('Detected chapter %s'%src) + self.log.debug(f'Detected chapter {src}') self.end_page() self.page_break_found = True diff --git a/src/calibre/ebooks/lrf/input.py b/src/calibre/ebooks/lrf/input.py index 876e9de36c..ca1d0c345e 100644 --- a/src/calibre/ebooks/lrf/input.py +++ b/src/calibre/ebooks/lrf/input.py @@ -29,7 +29,7 @@ class Canvas(etree.XSLTExtension): if cid is None or cid in self.processed: return self.processed.add(cid) - input_node = self.doc.xpath('//Canvas[@objid="%s"]'%cid)[0] + input_node = self.doc.xpath(f'//Canvas[@objid="{cid}"]')[0] objects = list(self.get_objects(input_node)) if len(objects) == 1 and objects[0][0].tag == 'ImageBlock': @@ -75,7 +75,7 @@ class Canvas(etree.XSLTExtension): img.set('height', str(int(height))) ref = block.get('refstream', None) if ref is not None: - imstr = self.doc.xpath('//ImageStream[@objid="%s"]'%ref) + imstr = self.doc.xpath(f'//ImageStream[@objid="{ref}"]') if imstr: src = imstr[0].get('file', None) if src: @@ -85,7 +85,7 @@ class Canvas(etree.XSLTExtension): def get_objects(self, node): for x in node.xpath('descendant::PutObj[@refobj and @x1 and @y1]'): - objs = node.xpath('//*[@objid="%s"]'%x.get('refobj')) + objs = node.xpath('//*[@objid="{}"]'.format(x.get('refobj'))) x, y = map(self.styles.to_num, (x.get('x1'), x.get('y1'))) if objs and x is not None and y is not None: yield objs[0], int(x), int(y) @@ -293,7 +293,7 @@ class Styles(etree.XSLTExtension): def write(self, name='styles.css'): def join(style): - ans = ['%s : %s;'%(k, v) for k, v in style.items()] + ans = [f'{k} : {v};' for k, v in style.items()] if ans: ans[-1] = ans[-1][:-1] return '\n\t'.join(ans) @@ -340,16 +340,16 @@ class Styles(etree.XSLTExtension): ans = {} sm = self.px_to_pt(node.get('sidemargin', None)) if sm is not None: - ans['margin-left'] = ans['margin-right'] = '%fpt'%sm + ans['margin-left'] = ans['margin-right'] = f'{sm:f}pt' ts = self.px_to_pt(node.get('topskip', None)) if ts is not None: - ans['margin-top'] = '%fpt'%ts + ans['margin-top'] = f'{ts:f}pt' fs = self.px_to_pt(node.get('footskip', None)) if fs is not None: - ans['margin-bottom'] = '%fpt'%fs + ans['margin-bottom'] = f'{fs:f}pt' fw = self.px_to_pt(node.get('framewidth', None)) if fw is not None: - ans['border-width'] = '%fpt'%fw + ans['border-width'] = f'{fw:f}pt' ans['border-style'] = 'solid' fc = self.color(node.get('framecolor', None)) if fc is not None: @@ -371,7 +371,7 @@ class Styles(etree.XSLTExtension): ans = {} fs = self.to_num(node.get('fontsize', None), 0.1) if fs is not None: - ans['font-size'] = '%fpt'%fs + ans['font-size'] = f'{fs:f}pt' fw = self.to_num(node.get('fontweight', None)) if fw is not None: ans['font-weight'] = ('bold' if fw >= 700 else 'normal') @@ -394,7 +394,7 @@ class Styles(etree.XSLTExtension): # ans['line-height'] = '%fpt'%lh pi = self.to_num(node.get('parindent', None), 0.1) if pi is not None: - ans['text-indent'] = '%fpt'%pi + ans['text-indent'] = f'{pi:f}pt' if not ans: return None if ans not in self.text_styles: diff --git a/src/calibre/ebooks/lrf/lrfparser.py b/src/calibre/ebooks/lrf/lrfparser.py index 59837c1718..80d9f6f7b5 100644 --- a/src/calibre/ebooks/lrf/lrfparser.py +++ b/src/calibre/ebooks/lrf/lrfparser.py @@ -87,25 +87,25 @@ class LRFDocument(LRFMetaFile): def to_xml(self, write_files=True): bookinfo = '\n\n\n' - bookinfo += '%s\n'%(self.metadata.title_reading, self.metadata.title) - bookinfo += '%s\n'%(self.metadata.author_reading, self.metadata.author) - bookinfo += '%s\n'%(self.metadata.book_id,) - bookinfo += '%s\n'%(self.metadata.publisher,) - bookinfo += '\n'%(self.metadata.label,) - bookinfo += '%s\n'%(self.metadata.category,) - bookinfo += '%s\n'%(self.metadata.classification,) - bookinfo += '%s\n\n\n'%(self.metadata.free_text,) + bookinfo += f'{self.metadata.title}\n' + bookinfo += f'{self.metadata.author}\n' + bookinfo += f'{self.metadata.book_id}\n' + bookinfo += f'{self.metadata.publisher}\n' + bookinfo += f'\n' + bookinfo += f'{self.metadata.category}\n' + bookinfo += f'{self.metadata.classification}\n' + bookinfo += f'{self.metadata.free_text}\n\n\n' th = self.doc_info.thumbnail if th: prefix = ascii_filename(self.metadata.title) - bookinfo += '\n'%(prefix+'_thumbnail.'+self.doc_info.thumbnail_extension,) + bookinfo += '\n'.format(prefix+'_thumbnail.'+self.doc_info.thumbnail_extension) if write_files: with open(prefix+'_thumbnail.'+self.doc_info.thumbnail_extension, 'wb') as f: f.write(th) - bookinfo += '%s\n'%(self.doc_info.language,) - bookinfo += '%s\n'%(self.doc_info.creator,) - bookinfo += '%s\n'%(self.doc_info.producer,) - bookinfo += '%s\n\n\n%s\n'%(self.doc_info.page,self.toc) + bookinfo += f'{self.doc_info.language}\n' + bookinfo += f'{self.doc_info.creator}\n' + bookinfo += f'{self.doc_info.producer}\n' + bookinfo += f'{self.doc_info.page}\n\n\n{self.toc}\n' pages = '' done_main = False pt_id = -1 diff --git a/src/calibre/ebooks/lrf/objects.py b/src/calibre/ebooks/lrf/objects.py index 286aac9994..32cde2a77f 100644 --- a/src/calibre/ebooks/lrf/objects.py +++ b/src/calibre/ebooks/lrf/objects.py @@ -200,11 +200,11 @@ class StyleObject: for h in self.tag_map.values(): attr = h[0] if hasattr(self, attr): - s += '%s="%s" '%(attr, getattr(self, attr)) + s += f'{attr}="{getattr(self, attr)}" ' return s def __str__(self): - s = '<%s objid="%s" stylelabel="%s" '%(self.__class__.__name__.replace('Attr', 'Style'), self.id, self.id) + s = '<{} objid="{}" stylelabel="{}" '.format(self.__class__.__name__.replace('Attr', 'Style'), self.id, self.id) s += self._tags_to_xml() s += '/>\n' return s @@ -252,7 +252,7 @@ class Color: self.a, self.r, self.g, self.b = val & 0xFF, (val>>8)&0xFF, (val>>16)&0xFF, (val>>24)&0xFF def __str__(self): - return '0x%02x%02x%02x%02x'%(self.a, self.r, self.g, self.b) + return f'0x{self.a:02x}{self.r:02x}{self.g:02x}{self.b:02x}' def __len__(self): return 4 @@ -280,8 +280,7 @@ class PageDiv(EmptyPageElement): self.linecolor = Color(linecolor) def __str__(self): - return '\n\n'%\ - (self.pain, self.spacesize, self.linewidth, self.color) + return f'\n\n' class RuledLine(EmptyPageElement): @@ -295,8 +294,7 @@ class RuledLine(EmptyPageElement): self.id = -1 def __str__(self): - return '\n\n'%\ - (self.linelength, self.linetype, self.linewidth, self.linecolor) + return f'\n\n' class Wait(EmptyPageElement): @@ -316,7 +314,7 @@ class Locate(EmptyPageElement): self.pos = self.pos_map[pos] def __str__(self): - return '\n\n'%(self.pos) + return f'\n\n' class BlockSpace(EmptyPageElement): @@ -470,7 +468,7 @@ class BlockAttr(StyleObject, LRFObject): if hasattr(obj, 'sidemargin'): margin = str(obj.sidemargin) + 'px' - ans += item('margin-left: %(m)s; margin-right: %(m)s;'%dict(m=margin)) + ans += item('margin-left: {m}; margin-right: {m};'.format(**dict(m=margin))) if hasattr(obj, 'topskip'): ans += item('margin-top: %dpx;'%obj.topskip) if hasattr(obj, 'footskip'): @@ -478,9 +476,9 @@ class BlockAttr(StyleObject, LRFObject): if hasattr(obj, 'framewidth'): ans += item('border: solid %dpx'%obj.framewidth) if hasattr(obj, 'framecolor') and obj.framecolor.a < 255: - ans += item('border-color: %s;'%obj.framecolor.to_html()) + ans += item(f'border-color: {obj.framecolor.to_html()};') if hasattr(obj, 'bgcolor') and obj.bgcolor.a < 255: - ans += item('background-color: %s;'%obj.bgcolor.to_html()) + ans += item(f'background-color: {obj.bgcolor.to_html()};') return ans @@ -506,19 +504,19 @@ class TextCSS: fn = getattr(obj, 'fontfacename', None) if fn is not None: fn = cls.FONT_MAP[fn] - ans += item('font-family: %s;'%fn) + ans += item(f'font-family: {fn};') fg = getattr(obj, 'textcolor', None) if fg is not None: fg = fg.to_html() - ans += item('color: %s;'%fg) + ans += item(f'color: {fg};') bg = getattr(obj, 'textbgcolor', None) if bg is not None: bg = bg.to_html() - ans += item('background-color: %s;'%bg) + ans += item(f'background-color: {bg};') al = getattr(obj, 'align', None) if al is not None: al = dict(head='left', center='center', foot='right') - ans += item('text-align: %s;'%al) + ans += item(f'text-align: {al};') lh = getattr(obj, 'linespace', None) if lh is not None: ans += item('text-align: %fpt;'%(int(lh)/10)) @@ -608,17 +606,17 @@ class Block(LRFStream, TextCSS): if hasattr(self, 'textstyle_id'): s += 'textstyle="%d" '%(self.textstyle_id,) for attr in self.attrs: - s += '%s="%s" '%(attr, self.attrs[attr]) + s += f'{attr}="{self.attrs[attr]}" ' if self.name != 'ImageBlock': s = s.rstrip()+'>\n' s += str(self.content) - s += '\n'%(self.name,) + s += f'\n' return s return s.rstrip() + ' />\n' def to_html(self): if self.name == 'TextBlock': - return '
%s
'%(self.style_id, self.textstyle_id, self.content.to_html()) + return f'
{self.content.to_html()}
' return '' @@ -689,9 +687,9 @@ class Text(LRFStream): self.self_closing = self_closing def __str__(self): - s = '<%s '%(self.name,) + s = f'<{self.name} ' for name, val in self.attrs.items(): - s += '%s="%s" '%(name, val) + s += f'{name}="{val}" ' return s.rstrip() + (' />' if self.self_closing else '>') def to_html(self): @@ -879,7 +877,7 @@ class Text(LRFStream): elif c is None: if open_containers: p = open_containers.pop() - s += ''%(p.name,) + s += f'' else: s += str(c) if not c.self_closing: @@ -887,9 +885,9 @@ class Text(LRFStream): if len(open_containers) > 0: if len(open_containers) == 1: - s += ''%(open_containers[0].name,) + s += f'' else: - raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],)) + raise LRFParseError(f'Malformed text stream {[i.name for i in open_containers if isinstance(i, Text.TextTag)]}') return s def to_html(self): @@ -913,7 +911,7 @@ class Text(LRFStream): open_containers.append(c) if len(open_containers) > 0: - raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],)) + raise LRFParseError(f'Malformed text stream {[i.name for i in open_containers if isinstance(i, Text.TextTag)]}') return s @@ -988,13 +986,13 @@ class Canvas(LRFStream): print('Canvas object has errors, skipping.') def __str__(self): - s = '\n<%s objid="%s" '%(self.__class__.__name__, self.id,) + s = f'\n<{self.__class__.__name__} objid="{self.id}" ' for attr in self.attrs: - s += '%s="%s" '%(attr, self.attrs[attr]) + s += f'{attr}="{self.attrs[attr]}" ' s = s.rstrip() + '>\n' for po in self: s += str(po) + '\n' - s += '\n'%(self.__class__.__name__,) + s += f'\n' return s def __iter__(self): @@ -1030,8 +1028,7 @@ class ImageStream(LRFStream): self._document.image_map[self.id] = self def __str__(self): - return '\n'%\ - (self.id, self.encoding, self.file) + return f'\n' class Import(LRFStream): @@ -1109,13 +1106,13 @@ class Button(LRFObject): return None, None def __str__(self): - s = '