diff --git a/imgsrc/srv/generate.py b/imgsrc/srv/generate.py index 7f2d870774..93c2cf9c2d 100644 --- a/imgsrc/srv/generate.py +++ b/imgsrc/srv/generate.py @@ -26,7 +26,7 @@ def clone_node(node, parent): def merge(): base = os.path.dirname(os.path.abspath(__file__)) ans = etree.fromstring( - '' % (SVG_NS, XLINK_NS), + ''.format(SVG_NS, XLINK_NS), parser=etree.XMLParser( recover=True, no_network=True, resolve_entities=False ) @@ -42,7 +42,7 @@ def merge(): recover=True, no_network=True, resolve_entities=False ) ) - symbol = ans.makeelement('{%s}symbol' % SVG_NS) + symbol = ans.makeelement('{{{}}}symbol'.format(SVG_NS)) symbol.set('viewBox', svg.get('viewBox')) symbol.set('id', 'icon-' + f.rpartition('.')[0]) for child in svg.iterchildren('*'): diff --git a/manual/conf.py b/manual/conf.py index fd72148cf3..bf9a699f04 100644 --- a/manual/conf.py +++ b/manual/conf.py @@ -97,7 +97,7 @@ today_fmt = '%B %d, %Y' unused_docs = ['global', 'cli/global'] locale_dirs = ['locale/'] -title = '%s User Manual' % __appname__ +title = '{} User Manual'.format(__appname__) needs_localization = language not in {'en', 'eng'} if needs_localization: import gettext @@ -253,5 +253,5 @@ latex_show_pagerefs = True latex_show_urls = 'footnote' latex_elements = { 'papersize':'letterpaper', - 'preamble': r'\renewcommand{\pageautorefname}{%s}' % _('page'), + 'preamble': r'\renewcommand{{\pageautorefname}}{{{}}}'.format(_('page')), } diff --git a/manual/custom.py b/manual/custom.py index 89a28a279e..2c71fefb3a 100644 --- a/manual/custom.py +++ b/manual/custom.py @@ -195,13 +195,13 @@ details and examples. lines = [] for cmd in COMMANDS: parser = option_parser_for(cmd)() - lines += ['.. _calibredb-%s-%s:' % (language, cmd), ''] + lines += ['.. _calibredb-{}-{}:'.format(language, cmd), ''] lines += [cmd, '~'*20, ''] usage = parser.usage.strip() usage = [i for i in usage.replace('%prog', 'calibredb').splitlines()] cmdline = ' '+usage[0] usage = usage[1:] - usage = [re.sub(r'(%s)([^a-zA-Z0-9])'%cmd, r':command:`\1`\2', i) for i in usage] + usage = [re.sub(r'({})([^a-zA-Z0-9])'.format(cmd), r':command:`\1`\2', i) for i in usage] lines += ['.. code-block:: none', '', cmdline, ''] lines += usage groups = [(None, None, parser.option_list)] @@ -257,7 +257,7 @@ def generate_ebook_convert_help(preamble, app): def update_cli_doc(name, raw, language): if isinstance(raw, bytes): raw = raw.decode('utf-8') - path = 'generated/%s/%s.rst' % (language, name) + path = 'generated/{}/{}.rst'.format(language, name) old_raw = open(path, encoding='utf-8').read() if os.path.exists(path) else '' if not os.path.exists(path) or old_raw != raw: import difflib @@ -352,7 +352,7 @@ def cli_docs(language): usage = [mark_options(i) for i in parser.usage.replace('%prog', cmd).splitlines()] cmdline = usage[0] usage = usage[1:] - usage = [i.replace(cmd, ':command:`%s`'%cmd) for i in usage] + usage = [i.replace(cmd, ':command:`{}`'.format(cmd)) for i in usage] usage = '\n'.join(usage) preamble = CLI_PREAMBLE.format(cmd=cmd, cmdref=cmd + '-' + language, cmdline=cmdline, usage=usage) if cmd == 'ebook-convert': @@ -382,7 +382,7 @@ def template_docs(language): def localized_path(app, langcode, pagename): href = app.builder.get_target_uri(pagename) - href = re.sub(r'generated/[a-z]+/', 'generated/%s/' % langcode, href) + href = re.sub(r'generated/[a-z]+/', 'generated/{}/'.format(langcode), href) prefix = '/' if langcode != 'en': prefix += langcode + '/' @@ -405,7 +405,7 @@ def setup_man_pages(app): documented_cmds = get_cli_docs()[0] man_pages = [] for cmd, option_parser in documented_cmds: - path = 'generated/%s/%s' % (app.config.language, cmd) + path = 'generated/{}/{}'.format(app.config.language, cmd) man_pages.append(( path, cmd, cmd, 'Kovid Goyal', 1 )) diff --git a/manual/epub.py b/manual/epub.py index 9fa088bc78..6b2224a888 100644 --- a/manual/epub.py +++ b/manual/epub.py @@ -49,7 +49,7 @@ class EPUBHelpBuilder(EpubBuilder): imgname = container.href_to_name(img.get('src'), name) fmt, width, height = identify(container.raw_data(imgname)) if width == -1: - raise ValueError('Failed to read size of: %s' % imgname) + raise ValueError('Failed to read size of: {}'.format(imgname)) img.set('style', 'width: %dpx; height: %dpx' % (width, height)) def fix_opf(self, container): diff --git a/ruff-strict-pep8.toml b/ruff-strict-pep8.toml index b2dc7b31c4..2c73e0fe95 100644 --- a/ruff-strict-pep8.toml +++ b/ruff-strict-pep8.toml @@ -17,12 +17,13 @@ exclude = [ quote-style = 'single' [lint] -ignore = ['E402', 'E722', 'E741', 'UP012', 'UP030', 'UP031', 'UP032', 'UP038'] +ignore = ['E402', 'E722', 'E741', 'UP012', 'UP030', 'UP032', 'UP038'] select = ['E', 'F', 'I', 'W', 'INT', 'Q', 'UP'] [lint.per-file-ignores] "recipes/*" = ['UP'] "manual/plugin_examples/*" = ['UP'] +"src/calibre/*" = ['UP031'] "src/calibre/ebooks/unihandecode/*codepoints.py" = ['E501'] "src/calibre/ebooks/metadata/sources/*" = ['UP'] "src/calibre/gui2/store/stores/*" = ['UP'] diff --git a/setup/__init__.py b/setup/__init__.py index 3553fb2dd1..1886deafc1 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -126,7 +126,7 @@ def initialize_constants(): with open(os.path.join(SRC, 'calibre/constants.py'), 'rb') as f: src = f.read().decode('utf-8') nv = re.search(r'numeric_version\s+=\s+\((\d+), (\d+), (\d+)\)', src) - __version__ = '%s.%s.%s'%(nv.group(1), nv.group(2), nv.group(3)) + __version__ = '{}.{}.{}'.format(nv.group(1), nv.group(2), nv.group(3)) __appname__ = re.search(r'__appname__\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src).group(2) with open(os.path.join(SRC, 'calibre/linux.py'), 'rb') as sf: diff --git a/setup/build.py b/setup/build.py index 868c419de8..8835a7d7d8 100644 --- a/setup/build.py +++ b/setup/build.py @@ -83,7 +83,7 @@ def lazy_load(name): try: return getattr(build_environment, name) except AttributeError: - raise ImportError('The setup.build_environment module has no symbol named: %s' % name) + raise ImportError('The setup.build_environment module has no symbol named: {}'.format(name)) def expand_file_list(items, is_paths=True, cross_compile_for='native'): @@ -617,8 +617,8 @@ class Build(Command): try: subprocess.check_call(*args, **kwargs) except: - cmdline = ' '.join(['"%s"' % (arg) if ' ' in arg else arg for arg in args[0]]) - print('Error while executing: %s\n' % (cmdline)) + cmdline = ' '.join(['"{}"'.format(arg) if ' ' in arg else arg for arg in args[0]]) + print('Error while executing: {}\n'.format(cmdline)) raise def build_headless(self): diff --git a/setup/build_environment.py b/setup/build_environment.py index 7b656c23e5..db5a17d2d0 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -113,7 +113,7 @@ qraw = subprocess.check_output([QMAKE, '-query']).decode('utf-8') def readvar(name): - return re.search('^%s:(.+)$' % name, qraw, flags=re.M).group(1).strip() + return re.search('^{}:(.+)$'.format(name), qraw, flags=re.M).group(1).strip() qt = {x:readvar(y) for x, y in {'libs':'QT_INSTALL_LIBS', 'plugins':'QT_INSTALL_PLUGINS'}.items()} diff --git a/setup/git_pre_commit_hook.py b/setup/git_pre_commit_hook.py index 90fb181d04..62be70ec45 100755 --- a/setup/git_pre_commit_hook.py +++ b/setup/git_pre_commit_hook.py @@ -56,11 +56,11 @@ class Bug: if int(bug) > 100000 and action != 'See': self.close_bug(bug, action) return match.group() + f' [{summary}]({LAUNCHPAD_BUG % bug})' - return match.group() + ' (%s)' % summary + return match.group() + ' ({})'.format(summary) return match.group() def close_bug(self, bug, action): - print('Closing bug #%s' % bug) + print('Closing bug #{}'.format(bug)) suffix = ( 'The fix will be in the next release. ' 'calibre is usually released every alternate Friday.' diff --git a/setup/git_version.py b/setup/git_version.py index 723067223c..feb3efeae7 100755 --- a/setup/git_version.py +++ b/setup/git_version.py @@ -24,7 +24,7 @@ class GitVersion(Command): nv = nv.replace('-', '.') except subprocess.CalledProcessError: raise SystemExit('Error: not a git checkout') - newsrc = re.sub(r'(git_version = ).*', r'\1%s' % repr(nv), src) + newsrc = re.sub(r'(git_version = ).*', r'\1{}'.format(repr(nv)), src) self.info('new version is:', nv) with open(constants_file, 'wb') as f: diff --git a/setup/gui.py b/setup/gui.py index 22ef575c8b..612bea854e 100644 --- a/setup/gui.py +++ b/setup/gui.py @@ -61,8 +61,8 @@ class GUI(Command): if self.newer(self.QRC, sources): self.info('Creating images.qrc') for s in sources: - files.append('%s'%s) - manifest = '\n\n%s\n\n'%'\n'.join(sorted(files)) + files.append('{}'.format(s)) + manifest = '\n\n{}\n\n'.format('\n'.join(sorted(files))) if not isinstance(manifest, bytes): manifest = manifest.encode('utf-8') with open('images.qrc', 'wb') as f: diff --git a/setup/hosting.py b/setup/hosting.py index 5750dccc0c..4ca36854f0 100644 --- a/setup/hosting.py +++ b/setup/hosting.py @@ -116,8 +116,7 @@ class SourceForge(Base): # {{{ try: check_call([ 'rsync', '-h', '-zz', '--progress', '-e', 'ssh -x', x, - '%s,%s@frs.sourceforge.net:%s' % - (self.username, self.project, self.rdir + '/') + '{},{}@frs.sourceforge.net:{}'.format(self.username, self.project, self.rdir + '/') ]) except KeyboardInterrupt: raise SystemExit(1) @@ -160,15 +159,14 @@ class GitHub(Base): # {{{ fname = os.path.basename(path) if fname in existing_assets: self.info( - 'Deleting %s from GitHub with id: %s' % - (fname, existing_assets[fname]) + 'Deleting {} from GitHub with id: {}'.format(fname, existing_assets[fname]) ) r = self.requests.delete(url.format(existing_assets[fname])) if r.status_code != 204: - self.fail(r, 'Failed to delete %s from GitHub' % fname) + self.fail(r, 'Failed to delete {} from GitHub'.format(fname)) r = self.do_upload(upload_url, path, desc, fname) if r.status_code != 201: - self.fail(r, 'Failed to upload file: %s' % fname) + self.fail(r, 'Failed to upload file: {}'.format(fname)) try: r = self.requests.patch( url.format(r.json()['id']), @@ -187,25 +185,22 @@ class GitHub(Base): # {{{ }) ) if r.status_code != 200: - self.fail(r, 'Failed to set label for %s' % fname) + self.fail(r, 'Failed to set label for {}'.format(fname)) def clean_older_releases(self, releases): for release in releases: if release.get('assets', None) and release['tag_name'] != self.current_tag_name: self.info( - '\nDeleting old released installers from: %s' % - release['tag_name'] + '\nDeleting old released installers from: {}'.format(release['tag_name']) ) for asset in release['assets']: r = self.requests.delete( - self.API + 'repos/%s/%s/releases/assets/%s' % - (self.username, self.reponame, asset['id']) + self.API + 'repos/{}/{}/releases/assets/{}'.format(self.username, self.reponame, asset['id']) ) if r.status_code != 204: self.fail( - r, 'Failed to delete obsolete asset: %s for release: %s' - % (asset['name'], release['tag_name']) + r, 'Failed to delete obsolete asset: {} for release: {}'.format(asset['name'], release['tag_name']) ) def do_upload(self, url, path, desc, fname): @@ -223,7 +218,7 @@ class GitHub(Base): # {{{ ) def fail(self, r, msg): - print(msg, ' Status Code: %s' % r.status_code, file=sys.stderr) + print(msg, ' Status Code: {}'.format(r.status_code), file=sys.stderr) print('JSON from response:', file=sys.stderr) pprint(dict(r.json()), stream=sys.stderr) raise SystemExit(1) @@ -260,14 +255,14 @@ class GitHub(Base): # {{{ data=json.dumps({ 'tag_name': self.current_tag_name, 'target_commitish': 'master', - 'name': 'version %s' % self.version, - 'body': 'Release version %s' % self.version, + 'name': 'version {}'.format(self.version), + 'body': 'Release version {}'.format(self.version), 'draft': False, 'prerelease': False }) ) if r.status_code != 201: - self.fail(r, 'Failed to create release for version: %s' % self.version) + self.fail(r, 'Failed to create release for version: {}'.format(self.version)) return r.json() @@ -325,12 +320,12 @@ def generate_index(): # {{{ ] body = ''.format(' '.join(body)) index = template.format( - title='Previous calibre releases (%s.x)' % sname, + title='Previous calibre releases ({}.x)'.format(sname), style=style, msg='Choose a calibre release', body=body ) - with open('%s.html' % sname, 'wb') as f: + with open('{}.html'.format(sname), 'wb') as f: f.write(index.encode('utf-8')) for r in releases: @@ -390,7 +385,7 @@ def generate_index(): # {{{ body = '
{}
'.format(''.join(body)) index = template.format( - title='calibre release (%s)' % rname, + title='calibre release ({})'.format(rname), style=style, msg='', body=body diff --git a/setup/hyphenation.py b/setup/hyphenation.py index 66a475054f..88517f62a1 100644 --- a/setup/hyphenation.py +++ b/setup/hyphenation.py @@ -84,7 +84,7 @@ class Hyphenation(ReVendor): NAME = 'hyphenation' TAR_NAME = 'hyphenation dictionaries' VERSION = 'master' - DOWNLOAD_URL = 'https://github.com/LibreOffice/dictionaries/archive/%s.tar.gz' % VERSION + DOWNLOAD_URL = 'https://github.com/LibreOffice/dictionaries/archive/{}.tar.gz'.format(VERSION) CAN_USE_SYSTEM_VERSION = False def run(self, opts): diff --git a/setup/install.py b/setup/install.py index 0d6c8d48e3..eb7d622775 100644 --- a/setup/install.py +++ b/setup/install.py @@ -288,14 +288,14 @@ class Install(Develop): class Sdist(Command): description = 'Create a source distribution' - DEST = os.path.join('dist', '%s-%s.tar.xz'%(__appname__, __version__)) + DEST = os.path.join('dist', '{}-{}.tar.xz'.format(__appname__, __version__)) def run(self, opts): if not self.e(self.d(self.DEST)): os.makedirs(self.d(self.DEST)) tdir = tempfile.mkdtemp() atexit.register(shutil.rmtree, tdir) - tdir = self.j(tdir, 'calibre-%s' % __version__) + tdir = self.j(tdir, 'calibre-{}'.format(__version__)) self.info('\tRunning git export...') os.mkdir(tdir) subprocess.check_call('git archive HEAD | tar -x -C ' + tdir, shell=True) @@ -336,7 +336,7 @@ class Sdist(Command): self.info('\tCreating tarfile...') dest = self.DEST.rpartition('.')[0] shutil.rmtree(os.path.join(tdir, '.github')) - subprocess.check_call(['tar', '--mtime=now', '-cf', self.a(dest), 'calibre-%s' % __version__], cwd=self.d(tdir)) + subprocess.check_call(['tar', '--mtime=now', '-cf', self.a(dest), 'calibre-{}'.format(__version__)], cwd=self.d(tdir)) self.info('\tCompressing tarfile...') if os.path.exists(self.a(self.DEST)): os.remove(self.a(self.DEST)) @@ -396,4 +396,4 @@ class Bootstrap(Command): subprocess.check_call(clone_cmd, cwd=self.d(self.SRC)) def run(self, opts): - self.info('\n\nAll done! You should now be able to run "%s setup.py install" to install calibre' % sys.executable) + self.info('\n\nAll done! You should now be able to run "{} setup.py install" to install calibre'.format(sys.executable)) diff --git a/setup/mathjax.py b/setup/mathjax.py index cf23e7b330..abc476d2c0 100644 --- a/setup/mathjax.py +++ b/setup/mathjax.py @@ -18,7 +18,7 @@ class MathJax(ReVendor): NAME = 'mathjax' TAR_NAME = 'MathJax' VERSION = '3.1.4' - DOWNLOAD_URL = 'https://github.com/mathjax/MathJax/archive/%s.tar.gz' % VERSION + DOWNLOAD_URL = 'https://github.com/mathjax/MathJax/archive/{}.tar.gz'.format(VERSION) def add_file_pre(self, name, raw): self.h.update(raw) diff --git a/setup/plugins_mirror.py b/setup/plugins_mirror.py index 1790b98b42..a240945b22 100644 --- a/setup/plugins_mirror.py +++ b/setup/plugins_mirror.py @@ -222,7 +222,7 @@ def get_import_data(name, mod, zf, names): return module raise ValueError(f'Failed to find name: {name!r} in module: {mod!r}') else: - raise ValueError('Failed to find module: %r' % mod) + raise ValueError('Failed to find module: {!r}'.format(mod)) def parse_metadata(raw, namelist, zf): @@ -372,7 +372,7 @@ def fetch_plugin(old_index, entry): raw = read(entry.url).decode('utf-8', 'replace') url, name = parse_plugin_zip_url(raw) if url is None: - raise ValueError('Failed to find zip file URL for entry: %s' % repr(entry)) + raise ValueError('Failed to find zip file URL for entry: {}'.format(repr(entry))) plugin = lm_map.get(entry.thread_id, None) if plugin is not None: @@ -392,7 +392,7 @@ def fetch_plugin(old_index, entry): slm = datetime(*parsedate(info.get('Last-Modified'))[:6]) plugin = get_plugin_info(raw) plugin['last_modified'] = slm.isoformat() - plugin['file'] = 'staging_%s.zip' % entry.thread_id + plugin['file'] = 'staging_{}.zip'.format(entry.thread_id) plugin['size'] = len(raw) plugin['original_url'] = url update_plugin_from_entry(plugin, entry) @@ -460,28 +460,28 @@ def plugin_to_index(plugin, count): quoteattr(plugin['thread_url']), escape(plugin['name'])) released = datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).strftime('%e %b, %Y').lstrip() details = [ - 'Version: %s' % escape('.'.join(map(str, plugin['version']))), - 'Released: %s' % escape(released), - 'Author: %s' % escape(plugin['author']), - 'calibre: %s' % escape('.'.join(map(str, plugin['minimum_calibre_version']))), - 'Platforms: %s' % escape(', '.join(sorted(plugin['supported_platforms']) or ['all'])), + 'Version: {}'.format(escape('.'.join(map(str, plugin['version'])))), + 'Released: {}'.format(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']))), ] if plugin['uninstall']: - details.append('Uninstall: %s' % escape(', '.join(plugin['uninstall']))) + details.append('Uninstall: {}'.format(escape(', '.join(plugin['uninstall'])))) if plugin['donate']: - details.append('Donate' % quoteattr(plugin['donate'])) + details.append('Donate'.format(quoteattr(plugin['donate']))) block = [] for li in details: if li.startswith('calibre:'): block.append('
') - block.append('
  • %s
  • ' % li) - block = '' % ('\n'.join(block)) + block.append('
  • {}
  • '.format(li)) + block = ''.format('\n'.join(block)) downloads = ('\xa0[%d total downloads]' % count) if count else '' zipfile = '
    Download plugin \u2193{}
    '.format( quoteattr(plugin['file']), quoteattr(plugin['name'] + '.zip'), downloads) desc = plugin['description'] or '' if desc: - desc = '

    %s

    ' % desc + desc = '

    {}

    '.format(desc) return f'{title}\n{desc}\n{block}\n{zipfile}\n\n' @@ -502,25 +502,25 @@ def create_index(index, raw_stats): Index of calibre plugins

    Index of calibre plugins

    Download counts for all plugins
    -%s +{} -''' % ('\n'.join(plugins)) +'''.format('\n'.join(plugins)) raw = index.encode('utf-8') try: with open('index.html', 'rb') as f: @@ -541,20 +541,20 @@ h1 { text-align: center } Stats for calibre plugins

    Stats for calibre plugins

    -%s +{}
    PluginTotal downloads
    - ''' % ('\n'.join(pstats)) + '''.format('\n'.join(pstats)) raw = stats.encode('utf-8') try: with open('stats.html', 'rb') as f: diff --git a/setup/publish.py b/setup/publish.py index 1a8cccf29d..ee1065d94d 100644 --- a/setup/publish.py +++ b/setup/publish.py @@ -68,7 +68,7 @@ class Stage2(Command): installer = self.j(self.d(self.SRC), installer) if not os.path.exists(installer) or os.path.getsize(installer) < 10000: raise SystemExit( - 'The installer %s does not exist' % os.path.basename(installer) + 'The installer {} does not exist'.format(os.path.basename(installer)) ) @@ -129,8 +129,7 @@ class PublishBetas(Command): def run(self, opts): dist = self.a(self.j(self.d(self.SRC), 'dist')) subprocess.check_call(( - 'rsync --partial -rh --info=progress2 --delete-after %s/ download.calibre-ebook.com:/srv/download/betas/' - % dist + 'rsync --partial -rh --info=progress2 --delete-after {}/ download.calibre-ebook.com:/srv/download/betas/'.format(dist) ).split()) @@ -203,7 +202,7 @@ class Manual(Command): jobs.append(create_job([ sys.executable, self.j(self.d(self.SRC), 'manual', 'build.py'), language, self.j(tdir, language) - ], '\n\n**************** Building translations for: %s' % language)) + ], '\n\n**************** Building translations for: {}'.format(language))) self.info('Building manual for %d languages' % len(jobs)) subprocess.check_call(jobs[0].cmd) if not parallel_build(jobs[1:], self.info): @@ -299,7 +298,7 @@ class ManPages(Command): for l in languages: jobs.append(create_job( [sys.executable, self.j(base, 'build.py'), '--man-pages', l, dest], - '\n\n**************** Building translations for: %s' % l) + '\n\n**************** Building translations for: {}'.format(l)) ) self.info(f'\tCreating man pages in {dest} for {len(jobs)} languages...') subprocess.check_call(jobs[0].cmd) diff --git a/setup/revendor.py b/setup/revendor.py index 5417de8074..2396907558 100755 --- a/setup/revendor.py +++ b/setup/revendor.py @@ -16,12 +16,12 @@ class ReVendor(Command): CAN_USE_SYSTEM_VERSION = True def add_options(self, parser): - parser.add_option('--path-to-%s' % self.NAME, help='Path to the extracted %s source' % self.TAR_NAME) - parser.add_option('--%s-url' % self.NAME, default=self.DOWNLOAD_URL, - help='URL to %s source archive in tar.gz format' % self.TAR_NAME) + parser.add_option('--path-to-{}'.format(self.NAME), help='Path to the extracted {} source'.format(self.TAR_NAME)) + parser.add_option('--{}-url'.format(self.NAME), default=self.DOWNLOAD_URL, + help='URL to {} source archive in tar.gz format'.format(self.TAR_NAME)) if self.CAN_USE_SYSTEM_VERSION: - parser.add_option('--system-%s' % self.NAME, default=False, action='store_true', - help='Treat %s as system copy and symlink instead of copy' % self.TAR_NAME) + parser.add_option('--system-{}'.format(self.NAME), default=False, action='store_true', + help='Treat {} as system copy and symlink instead of copy'.format(self.TAR_NAME)) def download_securely(self, url: str) -> bytes: num = 5 if is_ci else 1 @@ -35,7 +35,7 @@ class ReVendor(Command): time.sleep(2) def download_vendor_release(self, tdir, url): - self.info('Downloading %s:' % self.TAR_NAME, url) + self.info('Downloading {}:'.format(self.TAR_NAME), url) raw = self.download_securely(url) with tarfile.open(fileobj=BytesIO(raw)) as tf: tf.extractall(tdir) diff --git a/setup/test.py b/setup/test.py index 50d875053e..8a077845e6 100644 --- a/setup/test.py +++ b/setup/test.py @@ -37,14 +37,14 @@ class Test(BaseTest): super().add_options(parser) parser.add_option('--test-verbosity', type=int, default=4, help='Test verbosity (0-4)') parser.add_option('--test-module', '--test-group', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), - help='The test module to run (can be specified more than once for multiple modules). Choices: %s' % ', '.join(sorted(TEST_MODULES))) + help='The test module to run (can be specified more than once for multiple modules). Choices: {}'.format(', '.join(sorted(TEST_MODULES)))) parser.add_option('--test-name', '-n', default=[], action='append', help='The name of an individual test to run. Can be specified more than once for multiple tests. The name of the' ' test is the name of the test function without the leading test_. For example, the function test_something()' ' can be run by specifying the name "something".') parser.add_option('--exclude-test-module', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), help='A test module to be excluded from the test run (can be specified more than once for multiple modules).' - ' Choices: %s' % ', '.join(sorted(TEST_MODULES))) + ' Choices: {}'.format(', '.join(sorted(TEST_MODULES)))) parser.add_option('--exclude-test-name', default=[], action='append', help='The name of an individual test to be excluded from the test run. Can be specified more than once for multiple tests.') diff --git a/setup/translations.py b/setup/translations.py index 8f19527684..42122797a8 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -97,7 +97,7 @@ class POT(Command): # {{{ slash = codepoint_to_chr(92) msg = msg.replace(slash, slash*2).replace('"', r'\"').replace('\n', r'\n').replace('\r', r'\r').replace('\t', r'\t') - ans.append('msgid "%s"'%msg) + ans.append('msgid "{}"'.format(msg)) ans.append('msgstr ""') ans.append('') @@ -135,8 +135,8 @@ class POT(Command): # {{{ lines = f.read().decode('utf-8').splitlines() for i in range(len(lines)): line = lines[i].strip() - if line == '[calibre.%s]' % slug: - lines.insert(i+1, 'file_filter = manual//%s.po' % bname) + if line == '[calibre.{}]'.format(slug): + lines.insert(i+1, 'file_filter = manual//{}.po'.format(bname)) f.seek(0), f.truncate(), f.write('\n'.join(lines).encode('utf-8')) break else: @@ -397,12 +397,12 @@ class Translations(POT): # {{{ iso_data.extract_po_files('iso_639-3', tdir) for f, (locale, dest) in iteritems(fmap): iscpo = {'zh_HK':'zh_CN'}.get(locale, locale) - iso639 = self.j(tdir, '%s.po'%iscpo) + iso639 = self.j(tdir, '{}.po'.format(iscpo)) if os.path.exists(iso639): files.append((iso639, self.j(self.d(dest), 'iso639.mo'))) else: iscpo = iscpo.partition('_')[0] - iso639 = self.j(tdir, '%s.po'%iscpo) + iso639 = self.j(tdir, '{}.po'.format(iscpo)) if os.path.exists(iso639): files.append((iso639, self.j(self.d(dest), 'iso639.mo'))) elif locale not in skip_iso: diff --git a/setup/upload.py b/setup/upload.py index dbb5c56209..cddb1c22dd 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -74,7 +74,7 @@ def upload_signatures(): for srv in 'code main'.split(): check_call(scp + ['{0}:/srv/{0}/signatures/'.format(srv)]) check_call( - ['ssh', srv, 'chown', '-R', 'http:http', '/srv/%s/signatures' % srv] + ['ssh', srv, 'chown', '-R', 'http:http', '/srv/{}/signatures'.format(srv)] ) finally: shutil.rmtree(tdir) @@ -367,18 +367,18 @@ class UploadDemo(Command): # {{{ def run(self, opts): check_call( - '''ebook-convert %s/demo.html /tmp/html2lrf.lrf ''' + '''ebook-convert {}/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" ''' - '''''' % self.j(self.SRC, HTML2LRF), + ''''''.format(self.j(self.SRC, HTML2LRF)), shell=True ) lrf = self.j(self.SRC, 'calibre', 'ebooks', 'lrf', 'html', 'demo') check_call( - 'cd %s && zip -j /tmp/html-demo.zip * /tmp/html2lrf.lrf' % lrf, + 'cd {} && zip -j /tmp/html-demo.zip * /tmp/html2lrf.lrf'.format(lrf), shell=True ) @@ -409,8 +409,7 @@ class UploadToServer(Command): # {{{ ('ssh code /apps/update-calibre-version.py ' + __version__).split() ) check_call(( - 'ssh main /usr/local/bin/update-calibre-version.py %s && /usr/local/bin/update-calibre-code.py && /apps/static/generate.py' - % __version__ + 'ssh main /usr/local/bin/update-calibre-version.py {} && /usr/local/bin/update-calibre-code.py && /apps/static/generate.py'.format(__version__) ).split()) diff --git a/setup/win-ci.py b/setup/win-ci.py index ae1160ee11..1524154de1 100644 --- a/setup/win-ci.py +++ b/setup/win-ci.py @@ -100,7 +100,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' % sys.argv[-1]) + raise SystemExit('{!r} is not a valid action'.format(sys.argv[-1])) if __name__ == '__main__': diff --git a/src/odf/attrconverters.py b/src/odf/attrconverters.py index 945ea5fef8..46a948f2dd 100644 --- a/src/odf/attrconverters.py +++ b/src/odf/attrconverters.py @@ -50,7 +50,7 @@ pattern_vector3D = re.compile(r'\([ ]*-?([0-9]+(\.[0-9]*)?|\.[0-9]+)([ ]+-?([0-9 def make_NCName(arg): for c in (':',' '): - arg = arg.replace(c,'_%x_' % ord(c)) + arg = arg.replace(c,'_{:x}_'.format(ord(c))) return arg @@ -78,13 +78,13 @@ def cnv_color(attribute, arg, element): def cnv_configtype(attribute, arg, element): if unicode_type(arg) not in ('boolean', 'short', 'int', 'long', 'double', 'string', 'datetime', 'base64Binary'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) def cnv_data_source_has_labels(attribute, arg, element): if unicode_type(arg) not in ('none','row','column','both'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) # Understand different date formats @@ -116,7 +116,7 @@ def cnv_family(attribute, arg, element): ''' A style family ''' if unicode_type(arg) not in ('text', 'paragraph', 'section', 'ruby', 'table', 'table-column', 'table-row', 'table-cell', 'graphic', 'presentation', 'drawing-page', 'chart'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) @@ -154,7 +154,7 @@ def cnv_integer(attribute, arg, element): def cnv_legend_position(attribute, arg, element): if unicode_type(arg) not in ('start', 'end', 'top', 'bottom', 'top-start', 'bottom-start', 'top-end', 'bottom-end'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) @@ -167,7 +167,7 @@ def cnv_length(attribute, arg, element): ''' global pattern_length if not pattern_length.match(arg): - raise ValueError("'%s' is not a valid length" % arg) + raise ValueError("'{}' is not a valid length".format(arg)) return arg @@ -182,19 +182,19 @@ def cnv_lengthorpercent(attribute, arg, element): except: failed = True if failed: - raise ValueError("'%s' is not a valid length or percent" % arg) + raise ValueError("'{}' is not a valid length or percent".format(arg)) return arg def cnv_metavaluetype(attribute, arg, element): if unicode_type(arg) not in ('float', 'date', 'time', 'boolean', 'string'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) def cnv_major_minor(attribute, arg, element): if arg not in ('major','minor'): - raise ValueError("'%s' is not either 'minor' or 'major'" % arg) + raise ValueError("'{}' is not either 'minor' or 'major'".format(arg)) pattern_namespacedToken = re.compile(r'[0-9a-zA-Z_]+:[0-9a-zA-Z._\-]+') @@ -204,7 +204,7 @@ def cnv_namespacedToken(attribute, arg, element): global pattern_namespacedToken if not pattern_namespacedToken.match(arg): - raise ValueError("'%s' is not a valid namespaced token" % arg) + raise ValueError("'{}' is not a valid namespaced token".format(arg)) return __save_prefix(attribute, arg, element) @@ -258,7 +258,7 @@ pattern_percent = re.compile(r'-?([0-9]+(\.[0-9]*)?|\.[0-9]+)%') def cnv_percent(attribute, arg, element): global pattern_percent if not pattern_percent.match(arg): - raise ValueError("'%s' is not a valid length" % arg) + raise ValueError("'{}' is not a valid length".format(arg)) return arg @@ -277,7 +277,7 @@ def cnv_points(attribute, arg, element): try: strarg = ' '.join(['%d,%d' % p for p in arg]) except: - raise ValueError('Points must be string or [(0,0),(1,1)] - not %s' % arg) + raise ValueError('Points must be string or [(0,0),(1,1)] - not {}'.format(arg)) return strarg @@ -291,7 +291,7 @@ def cnv_string(attribute, arg, element): def cnv_textnoteclass(attribute, arg, element): if unicode_type(arg) not in ('footnote', 'endnote'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) # Understand different time formats @@ -317,7 +317,7 @@ def cnv_viewbox(attribute, arg, element): def cnv_xlinkshow(attribute, arg, element): if unicode_type(arg) not in ('new', 'replace', 'embed'): - raise ValueError("'%s' not allowed" % unicode_type(arg)) + raise ValueError("'{}' not allowed".format(unicode_type(arg))) return unicode_type(arg) diff --git a/src/odf/element.py b/src/odf/element.py index 27bba375b3..e43cbf6ddd 100644 --- a/src/odf/element.py +++ b/src/odf/element.py @@ -67,11 +67,11 @@ def _quoteattr(data, entities={}): data = _escape(data, entities) if '"' in data: if "'" in data: - data = '"%s"' % data.replace('"', '"') + data = '"{}"'.format(data.replace('"', '"')) else: - data = "'%s'" % data + data = "'{}'".format(data) else: - data = '"%s"' % data + data = '"{}"'.format(data) return data @@ -275,7 +275,7 @@ class CDATASection(Text, Childless): and then go into CDATA mode again. (' % self.data.replace(']]>',']]>]]>'.format(self.data.replace(']]>',']]>]]> element does not allow text' % self.tagName) + raise IllegalText('The <{}> element does not allow text'.format(self.tagName)) else: if text != '': self.appendChild(Text(text)) @@ -393,7 +393,7 @@ class Element(Node): Setting check_grammar=False turns off grammar checking ''' if check_grammar and self.qname not in grammar.allows_text: - raise IllegalText('The <%s> element does not allow text' % self.tagName) + raise IllegalText('The <{}> element does not allow text'.format(self.tagName)) else: self.appendChild(CDATASection(cdata)) diff --git a/src/odf/load.py b/src/odf/load.py index 14ef11f861..8773329642 100644 --- a/src/odf/load.py +++ b/src/odf/load.py @@ -75,7 +75,7 @@ class LoadParser(handler.ContentHandler): e = Element(qname=tag, qattributes=attrdict, check_grammar=False) self.curr = e except AttributeError as v: - print('Error: %s' % v) + print('Error: {}'.format(v)) if tag == (OFFICENS, 'automatic-styles'): e = self.doc.automaticstyles diff --git a/src/odf/odf2moinmoin.py b/src/odf/odf2moinmoin.py index 5ad46a4905..2e6e0d5c73 100644 --- a/src/odf/odf2moinmoin.py +++ b/src/odf/odf2moinmoin.py @@ -326,16 +326,16 @@ class ODF2MoinMoin: link = node.getAttribute('xlink:href') if link and link[:2] == './': # Indicates a sub-object, which isn't supported - return '%s\n' % link + return '{}\n'.format(link) if link and link[:9] == 'Pictures/': link = link[9:] - return '[[Image(%s)]]\n' % link + return '[[Image({})]]\n'.format(link) def text_a(self, node): text = self.textToString(node) link = node.getAttribute('xlink:href') if link.strip() == text.strip(): - return '[%s] ' % link.strip() + return '[{}] '.format(link.strip()) else: return f'[{link.strip()} {text.strip()}] ' @@ -348,7 +348,7 @@ class ODF2MoinMoin: body = (node.getElementsByTagName('text:note-body')[0] .childNodes[0]) self.footnotes.append((cite, self.textToString(body))) - return '^%s^' % cite + return '^{}^'.format(cite) def text_s(self, node): try: diff --git a/src/odf/odf2xhtml.py b/src/odf/odf2xhtml.py index 5fc575f1e8..920907e93f 100644 --- a/src/odf/odf2xhtml.py +++ b/src/odf/odf2xhtml.py @@ -161,7 +161,7 @@ class StyleToCSS: this should really be implemented as an absolutely position with a width and a height ''' - sdict['background-image'] = "url('%s')" % self.fillimages[val] + sdict['background-image'] = "url('{}')".format(self.fillimages[val]) def c_fo(self, ruleset, sdict, rule, val): ''' XSL formatting attributes ''' @@ -203,7 +203,7 @@ class StyleToCSS: if generic is not None: self.save_font(fontstyle, fontstyle, generic) family, htmlgeneric = self.fontdict.get(fontstyle, (fontstyle, 'serif')) - sdict['font-family'] = '%s, %s' % (family, htmlgeneric) + sdict['font-family'] = '{}, {}'.format(family, htmlgeneric) def c_text_position(self, ruleset, sdict, rule, tp): ''' Text position. This is used e.g. to make superscript and subscript @@ -510,7 +510,7 @@ class ODF2XHTML(handler.ContentHandler): if media: self.metatags.append(f'\n') else: - self.metatags.append('\n' % (stylefilename)) + self.metatags.append('\n'.format(stylefilename)) def _resetfootnotes(self): # Footnotes and endnotes @@ -564,7 +564,7 @@ class ODF2XHTML(handler.ContentHandler): for key,val in attrs.items(): a.append(f'''{key}={quoteattr(val)}''') if len(a) == 0: - self.writeout('<%s>' % tag) + self.writeout('<{}>'.format(tag)) else: self.writeout('<{} {}>'.format(tag, ' '.join(a))) if block: @@ -573,7 +573,7 @@ class ODF2XHTML(handler.ContentHandler): def closetag(self, tag, block=True): ''' Close an open HTML tag ''' self.htmlstack.pop() - self.writeout('' % tag) + self.writeout(''.format(tag)) if block: self.writeout('\n') @@ -675,14 +675,14 @@ class ODF2XHTML(handler.ContentHandler): ''' Set the content language. Identifies the targeted audience ''' self.language = ''.join(self.data) - self.metatags.append('\n' % escape(self.language)) + self.metatags.append('\n'.format(escape(self.language))) self.data = [] def e_dc_creator(self, tag, attrs): ''' Set the content creator. Identifies the targeted audience ''' self.creator = ''.join(self.data) - self.metatags.append('\n' % escape(self.creator)) + self.metatags.append('\n'.format(escape(self.creator))) self.data = [] def s_custom_shape(self, tag, attrs): @@ -923,7 +923,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } yield k, v for css2, names in css_styles.items(): - self.writeout('%s {\n' % ', '.join(names)) + self.writeout('{} {{\n'.format(', '.join(names))) for style, val in filter_margins(css2): self.writeout(f'\t{style}: {val};\n') self.writeout('}\n') @@ -970,7 +970,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } self.emptytag('meta', {'http-equiv':'Content-Type', 'content':'text/html;charset=UTF-8'}) for metaline in self.metatags: self.writeout(metaline) - self.writeout('%s\n' % escape(self.title)) + self.writeout('{}\n'.format(escape(self.title))) def e_office_document_content(self, tag, attrs): ''' Last tag ''' @@ -1172,7 +1172,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } c = attrs.get((TABLENS,'style-name'), None) if c and self.generate_css: c = c.replace('.','_') - self.opentag('table',{'class': 'T-%s' % c}) + self.opentag('table',{'class': 'T-{}'.format(c)}) else: self.opentag('table') self.purgedata() @@ -1198,7 +1198,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } c = attrs.get((TABLENS,'style-name')) if c: - htmlattrs['class'] = 'TD-%s' % c.replace('.','_') + htmlattrs['class'] = 'TD-{}'.format(c.replace('.','_')) self.opentag('td', htmlattrs) self.purgedata() @@ -1214,7 +1214,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } repeated = int(attrs.get((TABLENS,'number-columns-repeated'), 1)) htmlattrs = {} if c: - htmlattrs['class'] = 'TC-%s' % c.replace('.','_') + htmlattrs['class'] = 'TC-{}'.format(c.replace('.','_')) for x in range(repeated): self.emptytag('col', htmlattrs) self.purgedata() @@ -1225,7 +1225,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } c = attrs.get((TABLENS,'style-name'), None) htmlattrs = {} if c: - htmlattrs['class'] = 'TR-%s' % c.replace('.','_') + htmlattrs['class'] = 'TR-{}'.format(c.replace('.','_')) self.opentag('tr', htmlattrs) self.purgedata() @@ -1280,9 +1280,9 @@ dl.notes dd:last-of-type { page-break-after: avoid } self.headinglevels[x] = 0 special = special_styles.get('P-'+name) if special or not self.generate_css: - self.opentag('h%s' % level) + self.opentag('h{}'.format(level)) else: - self.opentag('h%s' % level, {'class':'P-%s' % name}) + self.opentag('h{}'.format(level), {'class':'P-{}'.format(name)}) self.purgedata() def e_text_h(self, tag, attrs): @@ -1309,7 +1309,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } self.closetag('a', False) self.opentag('a', {'id': anchor2}) self.closetag('a', False) - self.closetag('h%s' % level) + self.closetag('h{}'.format(level)) self.purgedata() def s_text_line_break(self, tag, attrs): @@ -1355,7 +1355,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } attrs = {'start': unicode_type(self.list_number_map[number_class])} if self.generate_css: attrs['class'] = list_class - self.opentag('%s' % tag_name, attrs) + self.opentag('{}'.format(tag_name), attrs) self.purgedata() def e_text_list(self, tag, attrs): @@ -1473,9 +1473,9 @@ dl.notes dd:last-of-type { page-break-after: avoid } self.notedict[self.currentnote]['citation'] = mark self.opentag('sup') self.opentag('a', { - 'href': '#footnote-%s' % self.currentnote, + 'href': '#footnote-{}'.format(self.currentnote), 'class': 'citation', - 'id':'citation-%s' % self.currentnote + 'id':'citation-{}'.format(self.currentnote) }) # self.writeout( escape(mark) ) # Since HTML only knows about endnotes, there is too much risk that the @@ -1496,7 +1496,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } if specialtag is None: specialtag = 'p' if self.generate_css: - htmlattrs['class'] = 'P-%s' % c + htmlattrs['class'] = 'P-{}'.format(c) self.opentag(specialtag, htmlattrs) self.purgedata() @@ -1548,7 +1548,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } if special is None: special = 'span' if self.generate_css: - htmlattrs['class'] = 'S-%s' % c + htmlattrs['class'] = 'S-{}'.format(c) self.opentag(special, htmlattrs) self.purgedata() diff --git a/src/odf/opendocument.py b/src/odf/opendocument.py index f53bb370cb..1c25eb2b97 100644 --- a/src/odf/opendocument.py +++ b/src/odf/opendocument.py @@ -388,7 +388,7 @@ class OpenDocument: document.folder = '%s/Object %d' % (self.folder, len(self.childobjects)) else: document.folder = objectname - return '.%s' % document.folder + return '.{}'.format(document.folder) def _savePictures(self, object, folder): for arcname, picturerec in object.Pictures.items(): @@ -492,23 +492,23 @@ class OpenDocument: else: self.manifest.addElement(manifest.FileEntry(fullpath=folder, mediatype=object.mimetype)) # Write styles - self.manifest.addElement(manifest.FileEntry(fullpath='%sstyles.xml' % folder, mediatype='text/xml')) - zi = zipfile.ZipInfo('%sstyles.xml' % folder, self._now) + self.manifest.addElement(manifest.FileEntry(fullpath='{}styles.xml'.format(folder), mediatype='text/xml')) + zi = zipfile.ZipInfo('{}styles.xml'.format(folder), self._now) zi.compress_type = zipfile.ZIP_DEFLATED zi.external_attr = UNIXPERMS self._z.writestr(zi, object.stylesxml()) # Write content - self.manifest.addElement(manifest.FileEntry(fullpath='%scontent.xml' % folder, mediatype='text/xml')) - zi = zipfile.ZipInfo('%scontent.xml' % folder, self._now) + self.manifest.addElement(manifest.FileEntry(fullpath='{}content.xml'.format(folder), mediatype='text/xml')) + zi = zipfile.ZipInfo('{}content.xml'.format(folder), self._now) zi.compress_type = zipfile.ZIP_DEFLATED zi.external_attr = UNIXPERMS self._z.writestr(zi, object.contentxml()) # Write settings if object.settings.hasChildNodes(): - self.manifest.addElement(manifest.FileEntry(fullpath='%ssettings.xml' % folder, mediatype='text/xml')) - zi = zipfile.ZipInfo('%ssettings.xml' % folder, self._now) + self.manifest.addElement(manifest.FileEntry(fullpath='{}settings.xml'.format(folder), mediatype='text/xml')) + zi = zipfile.ZipInfo('{}settings.xml'.format(folder), self._now) zi.compress_type = zipfile.ZIP_DEFLATED zi.external_attr = UNIXPERMS self._z.writestr(zi, object.settingsxml()) diff --git a/src/odf/userfield.py b/src/odf/userfield.py index bf4e5878ca..3c5cf15ce8 100644 --- a/src/odf/userfield.py +++ b/src/odf/userfield.py @@ -65,7 +65,7 @@ class UserFields: if isinstance(self.src_file, (bytes, str)): # src_file is a filename, check if it is a zip-file if not zipfile.is_zipfile(self.src_file): - raise TypeError('%s is no odt file.' % self.src_file) + raise TypeError('{} is no odt file.'.format(self.src_file)) elif self.src_file is None: # use stdin if no file given self.src_file = sys.stdin