From 54d1d4deb1a3c304333c8772b5d05819cc29ac45 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Mon, 21 Sep 2015 17:03:24 -0400 Subject: [PATCH 1/5] bash-completion: also complete epub files for calibre-debug This is actually probably somewhat more common than azw3. ;) --- src/calibre/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 340770dc51..9f38a888eb 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -573,7 +573,7 @@ def write_completion(bash_comp_dest, zsh): o_and_w('fetch-ebook-metadata', fem_op, []) o_and_w('calibre-smtp', smtp_op, []) o_and_w('calibre-server', serv_op, []) - o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'mobi', 'azw', 'azw3', 'docx'], file_map={ + o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'epub', 'mobi', 'azw', 'azw3', 'docx'], file_map={ '--tweak-book':['epub', 'azw3', 'mobi'], '--subset-font':['ttf', 'otf'], '--exec-file':['py', 'recipe'], From 05d42e0fe80e1416f8027d88c5431f5c04d507fa Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Sun, 15 Sep 2019 04:17:52 -0400 Subject: [PATCH 2/5] linux install: make bash completion fully python3 compliant This gets us closer to a state where polyglot hacks can be dropped, and is necessary to make per-command completion files not require even more hacks to proxy through polyglot_write. --- src/calibre/linux.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 9f38a888eb..7bea64b160 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -548,17 +548,16 @@ def write_completion(bash_comp_dest, zsh): complete = os.path.join(getattr(sys, 'frozen_path'), complete) with open(bash_comp_dest or os.devnull, 'wb') as f: - w = polyglot_write(f) def o_and_e(*args, **kwargs): - w(opts_and_exts(*args, **kwargs)) + f.write(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): - w(opts_and_words(*args, **kwargs)) + f.write(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) - w('# calibre Bash Shell Completion\n') + f.write(b'# calibre Bash Shell Completion\n') o_and_e('calibre', guiop, BOOK_EXTENSIONS) o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']}) o_and_e('ebook-meta', metaop, @@ -581,7 +580,7 @@ def write_completion(bash_comp_dest, zsh): '--inspect-mobi':['mobi', 'azw', 'azw3'], '--viewer':sorted(available_input_formats()), }) - w(textwrap.dedent(''' + f.write((textwrap.dedent(''' _ebook_device_ls() { local pattern search listing prefix @@ -654,7 +653,7 @@ def write_completion(bash_comp_dest, zsh): complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert - ''')%complete) + ''')%complete).encode('utf-8')) zsh.write() # }}} @@ -760,7 +759,7 @@ class PostInstall: appdata_resources=self.appdata_resources, frozen_path=getattr(sys, 'frozen_path', None)) try: with open(dest, 'wb') as f: - polyglot_write(f)(raw) + f.write(raw.encode('utf-8')) os.chmod(dest, stat.S_IRWXU|stat.S_IRGRP|stat.S_IROTH) if os.geteuid() == 0: os.chown(dest, 0, 0) @@ -858,21 +857,21 @@ class PostInstall: mimetypes.discard('application/octet-stream') def write_mimetypes(f): - polyglot_write(f)('MimeType=%s;\n'%';'.join(mimetypes)) + f.write(('MimeType=%s;\n'%';'.join(mimetypes)).encode('utf-8')) from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE with open('calibre-lrfviewer.desktop', 'wb') as f: - polyglot_write(f)(VIEWER) + f.write(VIEWER) with open('calibre-ebook-viewer.desktop', 'wb') as f: - polyglot_write(f)(EVIEWER) + f.write(EVIEWER) write_mimetypes(f) with open('calibre-ebook-edit.desktop', 'wb') as f: - polyglot_write(f)(ETWEAK) + f.write(ETWEAK) mt = {guess_type('a.' + x.lower())[0] for x in (SUPPORTED|IMPORTABLE)} - {None, 'application/octet-stream'} - polyglot_write(f)('MimeType=%s;\n'%';'.join(mt)) + f.write(('MimeType=%s;\n'%';'.join(mt)).encode('utf-8')) with open('calibre-gui.desktop', 'wb') as f: - polyglot_write(f)(GUI) + f.write(GUI) write_mimetypes(f) des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', 'calibre-ebook-viewer.desktop', 'calibre-ebook-edit.desktop') @@ -968,7 +967,7 @@ def opts_and_words(name, op, words, takes_files=False): esac } -complete -F _'''%(opts, words) + fname + ' ' + name +"\n\n") +complete -F _'''%(opts, words) + fname + ' ' + name +"\n\n").encode('utf-8') pics = {'jpg', 'jpeg', 'gif', 'png', 'bmp'} @@ -995,7 +994,7 @@ def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=(), extras.append(special_exts_template%(opt, eexts)) extras = '\n'.join(extras) - return '_'+fname+'()'+\ + return ('_'+fname+'()'+\ ''' { local cur prev opts @@ -1023,10 +1022,10 @@ def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=(), } complete -o filenames -F _'''%dict(pics=spics, - opts=opts, extras=extras, exts=exts) + fname + ' ' + name +"\n\n" + opts=opts, extras=extras, exts=exts) + fname + ' ' + name +"\n\n").encode('utf-8') -VIEWER = '''\ +VIEWER = b'''\ [Desktop Entry] Version=1.0 Type=Application @@ -1040,7 +1039,7 @@ MimeType=application/x-sony-bbeb; Categories=Graphics;Viewer; ''' -EVIEWER = '''\ +EVIEWER = b'''\ [Desktop Entry] Version=1.0 Type=Application @@ -1053,7 +1052,7 @@ Icon=calibre-viewer Categories=Graphics;Viewer; ''' -ETWEAK = '''\ +ETWEAK = b'''\ [Desktop Entry] Version=1.0 Type=Application @@ -1066,7 +1065,7 @@ Icon=calibre-ebook-edit Categories=Office; ''' -GUI = '''\ +GUI = b'''\ [Desktop Entry] Version=1.0 Type=Application From 19c4636677d31ffafd46c2a155dc540e18be5992 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Sun, 15 Sep 2019 03:53:48 -0400 Subject: [PATCH 3/5] linux: install bash completion as individual components In order to prevent bloating the shell with thousands of bash completion specs, bash completions are lazy-loaded as soon as the command is first tab-completed. This only works when the completionsdir contains a filename with the same name as the command being completed; as a result, calibre commands were able to be tab-completed only after 'calibre' was first completed. (This is unlike the zsh completions, which work when installed as a single unified file because zsh builds a cache of all known compdefs, and can load a completion on demand by reading from the file which contains it.) One common solution for programs which install several completions that share common helper functions is to install the completions in one file, and symlink all other command names to ensure the file is loaded by any name. I've opted for the other solution, which is to install each completion separately, since there is no common helper function to load once and use everywhere. As a result, there are some small speedups to be gained from only loading the completions being used. The main change is reindenting a lot of code to no longer be in a global context manager, but instead get written inside o_and_e/o_and_w. It's also necessary to write each individual completion file to the uninstaller, so do that. --- src/calibre/linux.py | 92 +++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 7bea64b160..b7d30caa65 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -516,13 +516,13 @@ def get_bash_completion_path(root, share, info): info('Failed to find directory to install bash completions, using default.') path = '/usr/share/bash-completion/completions' if path and os.path.exists(path) and os.path.isdir(path): - return os.path.join(path, 'calibre') + return path else: # Use the default bash-completion dir under staging_share - return os.path.join(share, 'bash-completion', 'completions', 'calibre') + return os.path.join(share, 'bash-completion', 'completions') -def write_completion(bash_comp_dest, zsh): +def write_completion(self, bash_comp_dest, zsh): from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop @@ -540,47 +540,51 @@ def write_completion(bash_comp_dest, zsh): input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED|IMPORTABLE) - if bash_comp_dest and not os.path.exists(os.path.dirname(bash_comp_dest)): - os.makedirs(os.path.dirname(bash_comp_dest)) + if bash_comp_dest and not os.path.exists(bash_comp_dest): + os.makedirs(bash_comp_dest) complete = 'calibre-complete' if getattr(sys, 'frozen_path', None): complete = os.path.join(getattr(sys, 'frozen_path'), complete) - with open(bash_comp_dest or os.devnull, 'wb') as f: + def o_and_e(name, *args, **kwargs): + bash_compfile = os.path.join(bash_comp_dest, name) + with open(bash_compfile, 'wb') as f: + f.write(opts_and_exts(name, *args, **kwargs)) + self.manifest.append(bash_compfile) + zsh.opts_and_exts(name, *args, **kwargs) - def o_and_e(*args, **kwargs): - f.write(opts_and_exts(*args, **kwargs)) - zsh.opts_and_exts(*args, **kwargs) + def o_and_w(name, *args, **kwargs): + bash_compfile = os.path.join(bash_comp_dest, name) + with open(bash_compfile, 'wb') as f: + f.write(opts_and_words(name, *args, **kwargs)) + self.manifest.append(bash_compfile) + zsh.opts_and_words(name, *args, **kwargs) - def o_and_w(*args, **kwargs): - f.write(opts_and_words(*args, **kwargs)) - zsh.opts_and_words(*args, **kwargs) - - f.write(b'# calibre Bash Shell Completion\n') - o_and_e('calibre', guiop, BOOK_EXTENSIONS) - o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']}) - o_and_e('ebook-meta', metaop, - list(meta_filetypes()), cover_opts=['--cover', '-c'], - opf_opts=['--to-opf', '--from-opf']) - o_and_e('ebook-polish', polish_op, - [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], - opf_opts=['--opf', '-o']) - o_and_e('lrfviewer', lrfviewerop, ['lrf']) - o_and_e('ebook-viewer', viewer_op, input_formats) - o_and_e('ebook-edit', tweak_op, tweak_formats) - o_and_w('fetch-ebook-metadata', fem_op, []) - o_and_w('calibre-smtp', smtp_op, []) - o_and_w('calibre-server', serv_op, []) - o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'epub', 'mobi', 'azw', 'azw3', 'docx'], file_map={ - '--tweak-book':['epub', 'azw3', 'mobi'], - '--subset-font':['ttf', 'otf'], - '--exec-file':['py', 'recipe'], - '--add-simple-plugin':['py'], - '--inspect-mobi':['mobi', 'azw', 'azw3'], - '--viewer':sorted(available_input_formats()), - }) - f.write((textwrap.dedent(''' + o_and_e('calibre', guiop, BOOK_EXTENSIONS) + o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']}) + o_and_e('ebook-meta', metaop, + list(meta_filetypes()), cover_opts=['--cover', '-c'], + opf_opts=['--to-opf', '--from-opf']) + o_and_e('ebook-polish', polish_op, + [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], + opf_opts=['--opf', '-o']) + o_and_e('lrfviewer', lrfviewerop, ['lrf']) + o_and_e('ebook-viewer', viewer_op, input_formats) + o_and_e('ebook-edit', tweak_op, tweak_formats) + o_and_w('fetch-ebook-metadata', fem_op, []) + o_and_w('calibre-smtp', smtp_op, []) + o_and_w('calibre-server', serv_op, []) + o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'epub', 'mobi', 'azw', 'azw3', 'docx'], file_map={ + '--tweak-book':['epub', 'azw3', 'mobi'], + '--subset-font':['ttf', 'otf'], + '--exec-file':['py', 'recipe'], + '--add-simple-plugin':['py'], + '--inspect-mobi':['mobi', 'azw', 'azw3'], + '--viewer':sorted(available_input_formats()), + }) + with open(os.path.join(bash_comp_dest, 'ebook-device'), 'wb') as f: + f.write(textwrap.dedent('''\ _ebook_device_ls() { local pattern search listing prefix @@ -650,10 +654,11 @@ def write_completion(bash_comp_dest, zsh): ;; esac } - complete -o nospace -F _ebook_device ebook-device - - complete -o nospace -C %s ebook-convert - ''')%complete).encode('utf-8')) + complete -o nospace -F _ebook_device ebook-device''').encode('utf-8')) + self.manifest.append(os.path.join(bash_comp_dest, 'ebook-device')) + with open(os.path.join(bash_comp_dest, 'ebook-convert'), 'wb') as f: + f.write(('complete -o nospace -C %s ebook-convert'%complete).encode('utf-8')) + self.manifest.append(os.path.join(bash_comp_dest, 'ebook-convert')) zsh.write() # }}} @@ -777,9 +782,8 @@ class PostInstall: self.manifest.append(zsh.dest) bash_comp_dest = get_bash_completion_path(self.opts.staging_root, os.path.dirname(self.opts.staging_sharedir), self.info) if bash_comp_dest is not None: - self.info('Installing bash completion to:', bash_comp_dest) - self.manifest.append(bash_comp_dest) - write_completion(bash_comp_dest, zsh) + self.info('Installing bash completion to:', bash_comp_dest+os.sep) + write_completion(self, bash_comp_dest, zsh) except TypeError as err: if 'resolve_entities' in unicode_type(err): print('You need python-lxml >= 2.0.5 for calibre') From 1cbdabbbb7ed599ec8a1b63cadcff3dea5082932 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Tue, 17 Sep 2019 23:46:31 -0400 Subject: [PATCH 4/5] linux install: make sure init_calibre.py is listed in calibre-uninstall It is written raw, not via write_template, so it was never added to the manifest. Therefore, it would remain behind when uninstalling calibre. Also change the order in which steps are executed, in order to write the env module -- and add it to the manifest -- before the uninstaller is created, rather than after (which would be too late). --- setup/install.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/install.py b/setup/install.py index 2be6b54e99..dc89c9fc3f 100644 --- a/setup/install.py +++ b/setup/install.py @@ -147,8 +147,8 @@ class Develop(Command): self.consolidate_paths() self.install_files() self.write_templates() - self.run_postinstall() self.install_env_module() + self.run_postinstall() self.success() def install_env_module(self): @@ -164,6 +164,7 @@ class Develop(Command): self.info('Installing calibre environment module: '+path) with open(path, 'wb') as f: f.write(HEADER.format(**self.template_args()).encode('utf-8')) + self.manifest.append(path) def install_files(self): pass From 53f30d9b7f1fd6cfb0971c2bf5f0e233ea4df062 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 30 Oct 2019 10:34:50 -0400 Subject: [PATCH 5/5] linux install: make file blobs start as unicode and get encoded while writing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kovid wants to be cautious about future refactoring maybe trying to modify them, and would prefer Thingsā„¢ be unicode for flexibility when using them, and only be bytes when actually write()'ing them. See https://github.com/kovidgoyal/calibre/pull/1065#issuecomment-547893895 --- src/calibre/linux.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index b7d30caa65..c37c15ab0c 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -866,16 +866,16 @@ class PostInstall: from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE with open('calibre-lrfviewer.desktop', 'wb') as f: - f.write(VIEWER) + f.write(VIEWER.encode('utf-8')) with open('calibre-ebook-viewer.desktop', 'wb') as f: - f.write(EVIEWER) + f.write(EVIEWER.encode('utf-8')) write_mimetypes(f) with open('calibre-ebook-edit.desktop', 'wb') as f: - f.write(ETWEAK) + f.write(ETWEAK.encode('utf-8')) mt = {guess_type('a.' + x.lower())[0] for x in (SUPPORTED|IMPORTABLE)} - {None, 'application/octet-stream'} f.write(('MimeType=%s;\n'%';'.join(mt)).encode('utf-8')) with open('calibre-gui.desktop', 'wb') as f: - f.write(GUI) + f.write(GUI.encode('utf-8')) write_mimetypes(f) des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', 'calibre-ebook-viewer.desktop', 'calibre-ebook-edit.desktop') @@ -1029,7 +1029,7 @@ complete -o filenames -F _'''%dict(pics=spics, opts=opts, extras=extras, exts=exts) + fname + ' ' + name +"\n\n").encode('utf-8') -VIEWER = b'''\ +VIEWER = '''\ [Desktop Entry] Version=1.0 Type=Application @@ -1043,7 +1043,7 @@ MimeType=application/x-sony-bbeb; Categories=Graphics;Viewer; ''' -EVIEWER = b'''\ +EVIEWER = '''\ [Desktop Entry] Version=1.0 Type=Application @@ -1056,7 +1056,7 @@ Icon=calibre-viewer Categories=Graphics;Viewer; ''' -ETWEAK = b'''\ +ETWEAK = '''\ [Desktop Entry] Version=1.0 Type=Application @@ -1069,7 +1069,7 @@ Icon=calibre-ebook-edit Categories=Office; ''' -GUI = b'''\ +GUI = '''\ [Desktop Entry] Version=1.0 Type=Application