diff --git a/misc/README b/misc/README
new file mode 100644
index 00000000..a7e703af
--- /dev/null
+++ b/misc/README
@@ -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 --git a/misc/_jp b/misc/_jp
new file mode 100644
index 00000000..27ddf6e7
--- /dev/null
+++ b/misc/_jp
@@ -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 "$@"