From 86494089df30a4ba8dd12170a2a03870b73ccd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 20 Oct 2024 18:13:44 +0200 Subject: [PATCH] Use stable ordering when writing shell completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set ordering is unstable (uses the hash for ordering), so the output file would be different every time. Sort the sets into lists before writing. In various places there were unnecessary "conversions", e.g. sorted() always returns a list, so 'list(sorted(…))' is not useful. In one place output is fixed to say: "Choices are: doc, ereader, ztxt" instead of "['doc', 'ereader', 'ztxt']". For module constants, just create the object directly. The code is easier to read/edit when the list is constructed directly with items that are ordered in the same way that is shown in various outputs (instead of being sorted dynamically). There is some more syntax confusion in src/calibre/linux.py. For example, '"%s" % (x)' is the same as '"%s" % x', because just putting something in parentheses does not create a tuple. It'd be nice to clean up this whole file to use f-strings and not the error-prone %-formatting, but that's a separate issue. In a test build of the Fedora package, the generated file is now stable. --- .../ebooks/conversion/plugins/pdb_output.py | 6 ++-- src/calibre/ebooks/pdb/__init__.py | 2 +- src/calibre/linux.py | 29 ++++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/pdb_output.py b/src/calibre/ebooks/conversion/plugins/pdb_output.py index 43354afa4d..a1cecafd82 100644 --- a/src/calibre/ebooks/conversion/plugins/pdb_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdb_output.py @@ -14,13 +14,13 @@ class PDBOutput(OutputFormatPlugin): author = 'John Schember' file_type = 'pdb' commit_name = 'pdb_output' - ui_data = {'formats': tuple(ALL_FORMAT_WRITERS)} + ui_data = {'formats': ALL_FORMAT_WRITERS} options = { OptionRecommendation(name='format', recommended_value='doc', level=OptionRecommendation.LOW, - short_switch='f', choices=list(ALL_FORMAT_WRITERS), - help=(_('Format to use inside the PDB container. Choices are:') + ' %s' % sorted(ALL_FORMAT_WRITERS))), + short_switch='f', choices=ALL_FORMAT_WRITERS, + help=(_('Format to use inside the PDB container. Choices are:') + ', '.join(ALL_FORMAT_WRITERS))), OptionRecommendation(name='pdb_output_encoding', recommended_value='cp1252', level=OptionRecommendation.LOW, help=_('Specify the character encoding of the output document. ' diff --git a/src/calibre/ebooks/pdb/__init__.py b/src/calibre/ebooks/pdb/__init__.py index ee8835f4de..b99ce1b3c2 100644 --- a/src/calibre/ebooks/pdb/__init__.py +++ b/src/calibre/ebooks/pdb/__init__.py @@ -31,7 +31,7 @@ def _import_readers(): } -ALL_FORMAT_WRITERS = {'doc', 'ztxt', 'ereader'} +ALL_FORMAT_WRITERS = ('doc', 'ereader', 'ztxt') # keep sorted alphabetically FORMAT_WRITERS = None diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 8b5e69be84..1237ffd413 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -289,11 +289,11 @@ class ZshCompleter: # {{{ elif opt.choices: arg += "(%s)"%'|'.join(opt.choices) elif set(file_map).intersection(set(opt._long_opts)): - k = set(file_map).intersection(set(opt._long_opts)) - exts = file_map[tuple(k)[0]] + k = list(set(file_map).intersection(set(opt._long_opts))) + exts = file_map[k[0]] if exts: - arg += "'_files -g \"%s\"'"%(' '.join('*.%s'%x for x in - tuple(exts) + tuple(x.upper() for x in exts))) + exts = ('*.%s'%x for x in sorted(exts + [x.upper() for x in exts])) + arg += "'_files -g \"%s\"'" % ' '.join(exts) else: arg += "_files" elif (opt.dest in {'pidfile', 'attachment'}): @@ -301,8 +301,8 @@ class ZshCompleter: # {{{ elif set(opf_opts).intersection(set(opt._long_opts)): arg += "'_files -g \"*.opf\"'" elif set(cover_opts).intersection(set(opt._long_opts)): - arg += "'_files -g \"%s\"'"%(' '.join('*.%s'%x for x in - tuple(pics) + tuple(x.upper() for x in pics))) + exts = ('*.%s'%x for x in sorted(pics + [x.upper() for x in pics])) + arg += "'_files -g \"%s\"'" % ' '.join(exts) help_txt = '"[%s]"'%h yield '%s%s%s%s '%(exclude, ostrings, help_txt, arg) @@ -332,10 +332,10 @@ class ZshCompleter: # {{{ from calibre.ebooks.conversion.plumber import supported_input_formats from calibre.utils.logging import DevNull from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles - input_fmts = set(supported_input_formats()) - output_fmts = set(available_output_formats()) - iexts = {x.upper() for x in input_fmts}.union(input_fmts) - oexts = {x.upper() for x in output_fmts}.union(output_fmts) + input_fmts = sorted(set(supported_input_formats())) + output_fmts = sorted(set(available_output_formats())) + iexts = sorted({x.upper() for x in input_fmts}.union(input_fmts)) + oexts = sorted({x.upper() for x in output_fmts}.union(output_fmts)) w = polyglot_write(f) # Arg 1 w('\n_ebc_input_args() {') @@ -425,7 +425,7 @@ class ZshCompleter: # {{{ from calibre.ebooks.oeb.polish.import_book import IMPORTABLE from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.gui2.tweak_book.main import option_parser - tweakable_fmts = SUPPORTED | IMPORTABLE + tweakable_fmts = sorted(SUPPORTED | IMPORTABLE) parser = option_parser() opt_lines = [] for opt in parser.option_list: @@ -499,7 +499,7 @@ _ebook_edit() {{ exts = [x.lower() for x in available_catalog_formats()] elif command == 'set_metadata': exts = ['opf'] - exts = set(exts).union(x.upper() for x in exts) + exts = sorted(set(exts).union(x.upper() for x in exts)) pats = ('*.%s'%x for x in exts) extra = ("'*:filename:_files -g \"%s\"' "%' '.join(pats),) if exts else () if command in {'add', 'add_format'}: @@ -917,6 +917,7 @@ class PostInstall: if mt and 'chemical' not in mt and 'ctc-posml' not in mt: mimetypes.add(mt) mimetypes.discard('application/octet-stream') + mimetypes = sorted(mimetypes) def write_mimetypes(f, extra=''): line = 'MimeType={};'.format(';'.join(mimetypes)) @@ -934,6 +935,7 @@ class PostInstall: with open('calibre-ebook-edit.desktop', 'wb') as f: f.write(ETWEAK.encode('utf-8')) mt = {guess_type('a.' + x.lower())[0] for x in (SUPPORTED|IMPORTABLE)} - {None, 'application/octet-stream'} + mt = sorted(mt) f.write(('MimeType=%s;\n'%';'.join(mt)).encode('utf-8')) with open('calibre-gui.desktop', 'wb') as f: f.write(GUI.encode('utf-8')) @@ -1032,8 +1034,7 @@ def opts_and_words(name, op, words, takes_files=False): complete -F _'''%(opts, words) + fname + ' ' + name +"\n\n").encode('utf-8') -pics = {'jpg', 'jpeg', 'gif', 'png', 'bmp'} -pics = list(sorted(pics)) # for reproducibility +pics = ['bmp', 'gif', 'jpeg', 'jpg', 'png'] # keep sorted alphabetically def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=(),