# HG changeset patch # User Goffi # Date 1392036826 -3600 # Node ID c328bcc4db7143bfc5a2ee129587d5b95a679e51 # Parent c39117d00f356ec6ed1436d1a9d9d5c956466bd1 jp: zsh completion, first draft (added in a new /misc directory): - completion automatically parse jp --help for the subcommand being completed to find optional and mandatory arguments - profiles are asked to jp himself (through jp profile list) - complete jp and jp_dev diff -r c39117d00f35 -r c328bcc4db71 misc/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/README Mon Feb 10 13:53:46 2014 +0100 @@ -0,0 +1,12 @@ +This directory contains files related to SàT but not directly used by it. + +* file _jp: + This is the completion file for zsh. To use it, you need to have it in a path accessible in your fpath variable, and to have completion activated. This can be done by the following commands in your .zshrc: + +### .zshrc completion ### +fpath=(/path/to/directory/with/_jp/ $fpath) +autoload -U compinit +compinit +### end of .zshrc completion ### + + Then, you should be able to complete a jp command line by pressing [TAB]. diff -r c39117d00f35 -r c328bcc4db71 misc/_jp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/_jp Mon Feb 10 13:53:46 2014 +0100 @@ -0,0 +1,141 @@ +#compdef jp jp_dev +# jp: a SAT command line tool +# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +#TODO: - caching (see _store_cache en _retrieve_cache) +# - filtering imposibles arguments +# - arguments (jids, files) + +PYTHON='python2' + +local optionals subcommands arguments +local context state state_descr line +typeset -A val_args + +_jp() { + eval `/usr/bin/env $PYTHON 2> /dev/null <<- PYTHONEND + import re + from subprocess import check_output + + # import sys + # words_raw="jp_dev " + ' '.join(sys.argv[1:]) # for debugging in a script + words_raw="$words" # $words is the command line currently completed + + words_all = words_raw.split() + prog_name = words_all[0] + + words_no_opt = [word for word in words_all if not word.startswith('-')] # command line without optional arguments + + choices_cache = {} + + ARG = r'[-a-z0-9_]' # charset accepted for an argument name + subcommands_re = re.compile(r"^ +{((?:" + ARG + r"+)(?:," + ARG + r"+)*)}", re.MULTILINE) + optionals_re = re.compile(r"^ {2,}(--?" + ARG + r"+(?: [A-Z_0-9]+)?(?:, --" + ARG + r"+(?: [A-Z_0-9]+)?)?)\n? {2,}(.*(?:\n {4,}.*)*$)", re.MULTILINE) + arguments_re = re.compile(r"^ {2,}([a-z_]" + ARG + r"*) {2,}(.*$)", re.MULTILINE) + clean_re = re.compile(r"(?P^ +)|(?P {2,})|(?P\n)|(?P')|(?P +$)", re.MULTILINE) + + def _clean(desc): + def sub_clean(match): + matched_dict = match.groupdict() + matched = {matched for matched in matched_dict if matched_dict[matched]} + if matched.intersection(('prefix_spaces', 'suffix_spaces')): + return '' + elif matched.intersection(('double_spaces', 'newline')): + return ' ' + elif matched.intersection(('quote',)): + return r"'\''" + else: + raise ValueError + return clean_re.sub(sub_clean, desc) + + def parse_help(jp_help): + # parse the help returning subcommands, optionals arguments, and mandatory arguments + subcommands = subcommands_re.findall(jp_help) + subcommands = {subcommand:"" for subcommand in subcommands[0].split(',')} if subcommands else {} + optionals = dict(optionals_re.findall(jp_help)) + arguments = dict(arguments_re.findall(jp_help)) + for subcommand in subcommands: + subcommands[subcommand] = arguments.pop(subcommand, '') + return subcommands, optionals, arguments + + def get_choice(opt_choice): + choices = choices_cache.get(opt_choice) + if choices is not None: + return choices + if opt_choice == 'PROFILE': + profiles = check_output([prog_name, 'profile', 'list']) + choices = ":profile:(%s)" % ' '.join(profiles.split('\n')) + if choices: + choices_cache[opt_choice] = choices + return choices + else: + return "" + + def construct_opt(opts, desc): + # construct zsh's _arguments line for optional arguments + arg_lines = [] + for opt in opts.split(', '): + try: + opt_name, opt_choice = opt.split() + except ValueError: + # there is no argument + opt_name, opt_choice = opt, None + # arg_lines.append("'()%s[%s]%s'" % (opt_name+('=' if opt_name.startswith('--') else '+'), + arg_lines.append("'()%s[%s]%s'" % (opt_name, + _clean(desc), + "%s" % get_choice(opt_choice) if opt_choice else '' + )) + return ' '.join(arg_lines) + + current_args = [] + + while True: + # parse jp's help recursively until words_no_opt doesn't correspond anymore to a subcommand + try: + current_args.append(words_no_opt.pop(0)) + jp_help = check_output(current_args + ['--help']) + # print "jp_help (%s):\n%s\n\n---\n" % (' '.join(current_args), jp_help) # for debugging + subcommands, optionals, arguments = parse_help(jp_help) + if words_no_opt[0] not in subcommands: + break + except IndexError: + break + + # now we fill the arrays so zsh can use them + env=[] + env.append("optionals=(%s)" % ' '.join(construct_opt(opt, desc) for opt, desc in optionals.items())) + env.append("subcommands=(%s)" % ' '.join(["'%s[%s]'" % (subcommand, _clean(desc)) for subcommand, desc in subcommands.items()])) + env.append("arguments=(%s)" % ' '.join(["'%s[%s]'" % (argument, _clean(desc)) for argument, desc in arguments.items()])) + + print ";".join(env) # this line is for eval + PYTHONEND + ` + + if [ -n "$optionals" ]; then + _values optional $optionals + + fi + if [ -n "$subcommands" ]; then + _values subcommand $subcommands + fi + if [ -n "$arguments" ]; then + #_values argument $arguments + fi +} + + +_jp "$@"