kislyuk / argcomplete

Python and tab completion, better together.
https://kislyuk.github.io/argcomplete/
Apache License 2.0
1.41k stars 133 forks source link

Static generation #85

Open wernight opened 9 years ago

wernight commented 9 years ago

I found myself wishing often to have a statically generated auto-complete, because:

As a pseudo-woraround I do $ register-python-argcomplete my-awesome-script.py >auto-complete-my-awesome-script.sh which is shipped with the software but this doesn't have all the benefits above.

The generator could read my current script once and generate a file to source for Bash and a file to add to fpath for ZSH.

kislyuk commented 9 years ago

Argcomplete is designed to be dynamic, as demonstrated in the network service-driven example in the readme. That doesn't completely exclude a static generator that you envision, but could you sketch out what the statically generated code would look like?

wernight commented 9 years ago

It'd look for example like:

File _mycommand for ZSH:

#compdef mycommand

_mycommand()
{
  # Those lines are to enable an action of the form '->state'.
  local curcontext="$curcontext"
  local state
  typeset -A opt_args

  # The option -C tells _arguments to modify the $curcontext parameter for 
  # an action of the form '->state'. This is the standard parameter used to
  # keep track of the current context.
  _arguments -C \
    {-h,--help}'[Help.]' \
    '--to=[Destination directory.]:Directories:_directories' \
    # *- indicates it can appear more than once:
    '*'{-u,--user}'=[Searches for clients owned by USER]:Usernames:_users' \
    # 1 means the first non-named argument
    '1:Client Names:->client' \
    '2:Target Countries:(France Germany Italy)'
    # '::' means it's optional:
    '*::Image:_files -g \*.\(ps\|eps\)'

  case $state in
    (client)
      compadd ‘John Doe’ 'Lorem Ipsum’
      compadd $(ls -m1)
    ;;      
  esac 
}

This is more of a complex example but I guess you get the point. All flags would be in that file. No need to run the Python script to get them, no need even to source the file for ZSH. Also subcommands can become functions which other users can use. Yes I know the hit would be only on the source and initial auto-complete and then cached. Still I do start terminals frequently and ZSH auto-complete fuzzy function means a lot more gets auto-completed.

innateessence commented 7 years ago

Here's a 30 min hack I wrote that seems relevant to the conversation/request although it was intended for dynamic use not static use, and there's 3 problems with it : 1 : There's no obvious way to resolve the filename afaik 2 : I'm notsure you could give this a dynamic name (IE #compdef *.py) 3 : It's horribly implemented, (but not a bad concept)

#compdef argparsecompleter

ARGS=`python<<EOF
import subprocess
import re

def parse(l):
    parsed_args = []
    for i in l:
        try:
            short_arg = re.search(r'-[a-zA-z+],', i).group().strip(',')
        except AttributeError:
            short_arg = ''
        try:
            long_arg = re.search(r'--[a-zA-z+]    ', i).group().strip('    ')
        except AttributeError:
            long_arg = ''
        try:
            help_text = re.search(r'    .+', i).group().lstrip('    ')
        except AttributeError:
            help_text = ''
        parsed_args.append('{}||{}||{}'.format(short_arg,long_arg,help_text))
    return parsed_args

def build_associative_array(l):
    associative_array = ""
    for i in l:
        i = i.split('||')
        short_arg = i[0]
        long_arg = i[1]
        help_text = i[2]
        if short_arg != '':
            associative_array = "{}[{}]\n".format(short_arg, help_text)
        associative_array = "{}[{}]\n".format(long_arg, help_text)
    return associative_array

filename = '' #TODO: Resolve filename

p = subprocess.Popen(['python', filename, '--help'], stdout=subprocess.PIPE).communicate()[0].decode('utf-8').splitlines()
for n, i in enumerate(p):
    if i == 'optional arguments:':
        start_position = n

args_list = p[start_position+1:]
args = parse(args_list)
args = build_associative_array(args)
print(args)
EOF`

typeset -A opt_args
local context state line

_arguments -s -S \
    $ARGS

It basically calls the script with the --help flag, parses the output, and builds an associative array to hand back to zsh.

If anyone wants to do anything with that, consider this my blessing. I originally started writing this when I couldn't get argcomplete to work for me and wanted a dynamic quick and dirty replacement, after writting what I posted here I decided it'd probably be a better option to just figure out why argcomplete isn't working for me so my hack is not complete (nor is it recommended, mostly just posting it as a concept)

lumbric commented 6 years ago

As reference: there is a project which can generate a completion script for bash and zsh using docopt: https://github.com/Infinidat/infi.docopt_completion

lumbric commented 6 years ago

Second reference: python-fire is generating a static shell script too.

Not sure if this helps, but maybe it is possible to copy some ideas from these projects.