Mercurial > libervia-backend
comparison misc/_jp @ 818:c328bcc4db71
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 10 Feb 2014 13:53:46 +0100 |
parents | |
children | 069ad98b360d |
comparison
equal
deleted
inserted
replaced
817:c39117d00f35 | 818:c328bcc4db71 |
---|---|
1 #compdef jp jp_dev | |
2 # jp: a SAT command line tool | |
3 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | |
4 | |
5 # This program is free software: you can redistribute it and/or modify | |
6 # it under the terms of the GNU Affero General Public License as published by | |
7 # the Free Software Foundation, either version 3 of the License, or | |
8 # (at your option) any later version. | |
9 | |
10 # This program is distributed in the hope that it will be useful, | |
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 # GNU Affero General Public License for more details. | |
14 | |
15 # You should have received a copy of the GNU Affero General Public License | |
16 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | |
18 | |
19 #TODO: - caching (see _store_cache en _retrieve_cache) | |
20 # - filtering imposibles arguments | |
21 # - arguments (jids, files) | |
22 | |
23 PYTHON='python2' | |
24 | |
25 local optionals subcommands arguments | |
26 local context state state_descr line | |
27 typeset -A val_args | |
28 | |
29 _jp() { | |
30 eval `/usr/bin/env $PYTHON 2> /dev/null <<- PYTHONEND | |
31 import re | |
32 from subprocess import check_output | |
33 | |
34 # import sys | |
35 # words_raw="jp_dev " + ' '.join(sys.argv[1:]) # for debugging in a script | |
36 words_raw="$words" # $words is the command line currently completed | |
37 | |
38 words_all = words_raw.split() | |
39 prog_name = words_all[0] | |
40 | |
41 words_no_opt = [word for word in words_all if not word.startswith('-')] # command line without optional arguments | |
42 | |
43 choices_cache = {} | |
44 | |
45 ARG = r'[-a-z0-9_]' # charset accepted for an argument name | |
46 subcommands_re = re.compile(r"^ +{((?:" + ARG + r"+)(?:," + ARG + r"+)*)}", re.MULTILINE) | |
47 optionals_re = re.compile(r"^ {2,}(--?" + ARG + r"+(?: [A-Z_0-9]+)?(?:, --" + ARG + r"+(?: [A-Z_0-9]+)?)?)\n? {2,}(.*(?:\n {4,}.*)*$)", re.MULTILINE) | |
48 arguments_re = re.compile(r"^ {2,}([a-z_]" + ARG + r"*) {2,}(.*$)", re.MULTILINE) | |
49 clean_re = re.compile(r"(?P<prefix_spaces>^ +)|(?P<double_spaces> {2,})|(?P<newline>\n)|(?P<quote>')|(?P<suffix_spaces> +$)", re.MULTILINE) | |
50 | |
51 def _clean(desc): | |
52 def sub_clean(match): | |
53 matched_dict = match.groupdict() | |
54 matched = {matched for matched in matched_dict if matched_dict[matched]} | |
55 if matched.intersection(('prefix_spaces', 'suffix_spaces')): | |
56 return '' | |
57 elif matched.intersection(('double_spaces', 'newline')): | |
58 return ' ' | |
59 elif matched.intersection(('quote',)): | |
60 return r"'\''" | |
61 else: | |
62 raise ValueError | |
63 return clean_re.sub(sub_clean, desc) | |
64 | |
65 def parse_help(jp_help): | |
66 # parse the help returning subcommands, optionals arguments, and mandatory arguments | |
67 subcommands = subcommands_re.findall(jp_help) | |
68 subcommands = {subcommand:"" for subcommand in subcommands[0].split(',')} if subcommands else {} | |
69 optionals = dict(optionals_re.findall(jp_help)) | |
70 arguments = dict(arguments_re.findall(jp_help)) | |
71 for subcommand in subcommands: | |
72 subcommands[subcommand] = arguments.pop(subcommand, '') | |
73 return subcommands, optionals, arguments | |
74 | |
75 def get_choice(opt_choice): | |
76 choices = choices_cache.get(opt_choice) | |
77 if choices is not None: | |
78 return choices | |
79 if opt_choice == 'PROFILE': | |
80 profiles = check_output([prog_name, 'profile', 'list']) | |
81 choices = ":profile:(%s)" % ' '.join(profiles.split('\n')) | |
82 if choices: | |
83 choices_cache[opt_choice] = choices | |
84 return choices | |
85 else: | |
86 return "" | |
87 | |
88 def construct_opt(opts, desc): | |
89 # construct zsh's _arguments line for optional arguments | |
90 arg_lines = [] | |
91 for opt in opts.split(', '): | |
92 try: | |
93 opt_name, opt_choice = opt.split() | |
94 except ValueError: | |
95 # there is no argument | |
96 opt_name, opt_choice = opt, None | |
97 # arg_lines.append("'()%s[%s]%s'" % (opt_name+('=' if opt_name.startswith('--') else '+'), | |
98 arg_lines.append("'()%s[%s]%s'" % (opt_name, | |
99 _clean(desc), | |
100 "%s" % get_choice(opt_choice) if opt_choice else '' | |
101 )) | |
102 return ' '.join(arg_lines) | |
103 | |
104 current_args = [] | |
105 | |
106 while True: | |
107 # parse jp's help recursively until words_no_opt doesn't correspond anymore to a subcommand | |
108 try: | |
109 current_args.append(words_no_opt.pop(0)) | |
110 jp_help = check_output(current_args + ['--help']) | |
111 # print "jp_help (%s):\n%s\n\n---\n" % (' '.join(current_args), jp_help) # for debugging | |
112 subcommands, optionals, arguments = parse_help(jp_help) | |
113 if words_no_opt[0] not in subcommands: | |
114 break | |
115 except IndexError: | |
116 break | |
117 | |
118 # now we fill the arrays so zsh can use them | |
119 env=[] | |
120 env.append("optionals=(%s)" % ' '.join(construct_opt(opt, desc) for opt, desc in optionals.items())) | |
121 env.append("subcommands=(%s)" % ' '.join(["'%s[%s]'" % (subcommand, _clean(desc)) for subcommand, desc in subcommands.items()])) | |
122 env.append("arguments=(%s)" % ' '.join(["'%s[%s]'" % (argument, _clean(desc)) for argument, desc in arguments.items()])) | |
123 | |
124 print ";".join(env) # this line is for eval | |
125 PYTHONEND | |
126 ` | |
127 | |
128 if [ -n "$optionals" ]; then | |
129 _values optional $optionals | |
130 | |
131 fi | |
132 if [ -n "$subcommands" ]; then | |
133 _values subcommand $subcommands | |
134 fi | |
135 if [ -n "$arguments" ]; then | |
136 #_values argument $arguments | |
137 fi | |
138 } | |
139 | |
140 | |
141 _jp "$@" |