diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index a78e40fe67..c1f4d46f79 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -100,6 +100,9 @@ def option_recommendation_to_cli_option(add_option, rec): switches = ['--disable-'+opt.long_switch] add_option(Option(*switches, **attrs)) +def group_titles(): + return _('INPUT OPTIONS'), _('OUTPUT OPTIONS') + def add_input_output_options(parser, plumber): input_options, output_options = \ plumber.input_options, plumber.output_options @@ -109,14 +112,14 @@ def add_input_output_options(parser, plumber): option_recommendation_to_cli_option(group, opt) if input_options: - title = _('INPUT OPTIONS') + title = group_titles()[0] io = OptionGroup(parser, title, _('Options to control the processing' ' of the input %s file')%plumber.input_fmt) add_options(io.add_option, input_options) parser.add_option_group(io) if output_options: - title = _('OUTPUT OPTIONS') + title = group_titles()[1] oo = OptionGroup(parser, title, _('Options to control the processing' ' of the output %s')%plumber.output_fmt) add_options(oo.add_option, output_options) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index c50bf52f84..9a4b87c03a 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -137,9 +137,12 @@ class ZshCompleter(object): # {{{ def get_options(self, parser, cover_opts=('--cover',), opf_opts=('--opf',), file_map={}): - options = parser.option_list - for group in parser.option_groups: - options += group.option_list + if hasattr(parser, 'option_list'): + options = parser.option_list + for group in parser.option_groups: + options += group.option_list + else: + options = parser for opt in options: lo, so = opt._long_opts, opt._short_opts if opt.takes_value(): @@ -160,7 +163,7 @@ class ZshCompleter(object): # {{{ arg = '' if opt.takes_value(): arg = ':"%s":'%h - if opt.dest in {'to_dir', 'outbox', 'with_library', 'library_path'}: + if opt.dest in {'debug_pipeline', 'to_dir', 'outbox', 'with_library', 'library_path'}: arg += "'_path_files -/'" elif opt.choices: arg += "(%s)"%'|'.join(opt.choices) @@ -201,6 +204,98 @@ class ZshCompleter(object): # {{{ txt = '_arguments -s \\\n ' + opts self.commands[name] = txt + def do_ebook_convert(self, f): + from calibre.ebooks.conversion.plumber import supported_input_formats + from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles + from calibre.customize.ui import available_output_formats + from calibre.ebooks.conversion.cli import create_option_parser, group_titles + from calibre.utils.logging import DevNull + 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) + w = lambda x: f.write(x if isinstance(x, bytes) else x.encode('utf-8')) + # Arg 1 + w('\n_ebc_input_args() {') + w('\n local extras; extras=(') + w('\n {-h,--help}":Show Help"') + w('\n "--version:Show program version"') + w('\n "--list-recipes:List builtin recipe names"') + for recipe in sorted(set(get_builtin_recipe_titles())): + recipe = recipe.replace(':', '\\:').replace('"', '\\"') + w(u'\n "%s.recipe"'%(recipe)) + w('\n ); _describe -t recipes "ebook-convert builtin recipes" extras') + w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in iexts))) + w('\n}\n') + + # Arg 2 + w('\n_ebc_output_args() {') + w('\n local extras; extras=(') + for x in output_fmts: + w('\n ".{0}:Convert to a .{0} file with the same name as the input file"'.format(x)) + w('\n ); _describe -t output "ebook-convert output" extras') + w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in oexts))) + w('\n _path_files -/') + w('\n}\n') + + log = DevNull() + def get_parser(input_fmt='epub', output_fmt=None): + of = ('dummy2.'+output_fmt) if output_fmt else 'dummy' + return create_option_parser(('ec', 'dummy1.'+input_fmt, of, '-h'), log)[0] + + # Common options + input_group, output_group = group_titles() + p = get_parser() + opts = p.option_list + for group in p.option_groups: + if group.title not in {input_group, output_group}: + opts += group.option_list + opts.append(p.get_option('--pretty-print')) + opts.append(p.get_option('--input-encoding')) + opts = '\\\n '.join(tuple( + self.get_options(opts, file_map={'--search-replace':()}))) + w('\n_ebc_common_opts() {') + w('\n _arguments -s \\\n ' + opts) + w('\n}\n') + + # Input/Output format options + for fmts, group_title, func in ( + (input_fmts, input_group, '_ebc_input_opts_%s'), + (output_fmts, output_group, '_ebc_output_opts_%s'), + ): + for fmt in fmts: + is_input = group_title == input_group + if is_input and fmt in {'rar', 'zip', 'oebzip'}: continue + p = (get_parser(input_fmt=fmt) if is_input + else get_parser(output_fmt=fmt)) + opts = None + for group in p.option_groups: + if group.title == group_title: + opts = [o for o in group.option_list if + '--pretty-print' not in o._long_opts and + '--input-encoding' not in o._long_opts] + if not opts: continue + opts = '\\\n '.join(tuple(self.get_options(opts))) + w('\n%s() {'%(func%fmt)) + w('\n _arguments -s \\\n ' + opts) + w('\n}\n') + + w('\n_ebook_convert() {') + w('\n local iarg oarg context state_descr state line\n typeset -A opt_args\n local ret=1') + w("\n _arguments '1: :_ebc_input_args' '*::ebook-convert output:->args' && ret=0") + w("\n case $state in \n (args)") + w('\n iarg=${line[1]##*.}; ') + w("\n _arguments '1: :_ebc_output_args' '*::ebook-convert options:->args' && ret=0") + w("\n case $state in \n (args)") + + w('\n oarg=${line[1]##*.}') + w('\n iarg="_ebc_input_opts_${(L)iarg}"; oarg="_ebc_output_opts_${(L)oarg}"') + w('\n _call_function - $iarg; _call_function - $oarg; _ebc_common_opts; ret=0') + w('\n ;;\n esac') + + w("\n ;;\n esac\n return ret") + w('\n}\n') + def do_calibredb(self, f): import calibre.library.cli as cli from calibre.customize.ui import available_catalog_formats @@ -246,13 +341,13 @@ class ZshCompleter(object): # {{{ f.write('\n_calibredb() {') f.write( r''' - local context curcontext="$curcontext" state line + local state line state_descr context typeset -A opt_args local ret=1 - _arguments -C \ + _arguments \ '1: :_calibredb_cmds' \ - '*::arg:->args' \ + '*::calibredb subcommand options:->args' \ && ret=0 case $state in @@ -273,8 +368,10 @@ class ZshCompleter(object): # {{{ def write(self): if self.dest: self.commands['calibredb'] = ' _calibredb "$@"' + self.commands['ebook-convert'] = ' _ebook_convert "$@"' with open(self.dest, 'wb') as f: f.write('#compdef ' + ' '.join(self.commands)+'\n') + self.do_ebook_convert(f) self.do_calibredb(f) f.write('case $service in\n') for c, txt in self.commands.iteritems(): @@ -380,7 +477,7 @@ class PostInstall: def setup_completion(self): # {{{ try: - self.info('Setting up bash/zsh completion...') + self.info('Setting up command-line completion...') 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 @@ -808,3 +905,4 @@ def main(): if __name__ == '__main__': sys.exit(main()) + sys.exit(main()) diff --git a/src/calibre/utils/logging.py b/src/calibre/utils/logging.py index e9132e079a..5f395e4f7b 100644 --- a/src/calibre/utils/logging.py +++ b/src/calibre/utils/logging.py @@ -28,7 +28,6 @@ class Stream(object): def flush(self): self.stream.flush() - class ANSIStream(Stream): def __init__(self, stream=sys.stdout): @@ -164,6 +163,12 @@ class Log(object): def __call__(self, *args, **kwargs): self.prints(INFO, *args, **kwargs) +class DevNull(Log): + + def __init__(self): + Log.__init__(self, level=Log.ERROR) + self.outputs = [] + class ThreadSafeLog(Log): def __init__(self, level=Log.INFO):