Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions zshgencomp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Parse getopt-style help texts for options
# and generate zsh(1) completion function.
# http://github.com/RobSis/zsh-completion-generator

import os
import sys
import re
import argparse
import subprocess
from string import Template


URL = 'http://github.com/RobSis/zsh-completion-generator'
STRIP_CHARS = " \t\n,="
HELP_MSG = """
Parse getopt-style help texts for options
and generate zsh(1) completion function.
http://github.com/RobSis/zsh-completion-generator

Usage:
zshgencomp help Show this page.
zshgencomp COMMAND [OPTIONS] Generate zsh completion.
Options:
-c HELP_COMMAND Run custom help command (default: --help).

The completions will be written to STDOUT.
Direct it into a file instead and place it into any
directory in your $FPATH to get nice zsh completion.

Examples:
zshgencomp ack
zshgencomp vim -c "-h"
"""

COMPLETE_FUNCTION_TEMPLATE = """
#compdef $program_name

# zsh completions for '$program_name'
# automatically generated with $url
local arguments

arguments=(
$argument_list
'*:filename:_files'
)

_arguments -s $arguments
"""

ARGUMENT_TEMPLATE = """ {$opts}'[$description]$style'"""
SINGLE_ARGUMENT_TEMPLATE = """ '$opt[$description]$style'"""


def cut_option(line):
"""
Cuts out the first option (short or long) and its argument.
"""
# TODO: dare to make it regex-free?
newline = line.strip(STRIP_CHARS)
opt = re.findall(r'^(-[a-zA-Z0-9\-]+(?:[\[\ =][^\-\ ][a-zA-Z\<\>\[\|\:\]\-\_\?#]*\]?)?)', line)
if len(opt) > 0:
newline = line.replace(opt[0], "", 1).strip(STRIP_CHARS)
# return without parameter
return newline, re.split('[\ \[=]', opt[0])[0]
else:
return newline, None


def parse_options(help_text):
"""
Parses the options line by line.
When description is missing and options are missing on
consecutive line, link them together.
"""
all_options = []
previous_description_missing = False
for line in help_text:
line = line.strip(STRIP_CHARS)
if re.match(r'^--?[a-zA-Z0-9]+', line) != None: # starts with option
previous_description_missing = False
options = []
while True:
line, opt = cut_option(line)
if opt == None:
break

options.append(opt)

if (len(line) == 0):
previous_description_missing = True

options.append(line)
all_options.append(options)
elif previous_description_missing:
all_options[-1][-1] = line
previous_description_missing = False

return all_options


def _escape(line):
"""
Escape the syntax-breaking characters.
"""
line = line.replace('[','\[').replace(']','\]')
line = re.sub('\'', '', line) # ' is unescapable afaik
return line


def generate_argument_list(options):
"""
Generate list of arguments from the template.
"""
argument_list = []
for opts in options:
model = {}
# remove unescapable chars.

model['description'] = _escape(opts[-1])
model['style'] = ""
if (len(opts) > 2):
model['opts'] = ",".join(opts[:-1])
argument_list.append(Template(ARGUMENT_TEMPLATE).safe_substitute(model))
elif (len(opts) == 2):
model['opt'] = opts[0]
argument_list.append(Template(SINGLE_ARGUMENT_TEMPLATE).safe_substitute(model))
else:
pass

return "\n".join(argument_list)


def generate_completion_function(options, program_name):
"""
Generate completion function from the template.
"""
model = {}
model['program_name'] = program_name
model['argument_list'] = generate_argument_list(options)
model['url'] = URL
return Template(COMPLETE_FUNCTION_TEMPLATE).safe_substitute(model).strip()

def determine_command_options(command_line_args):
"""
Determines and runs appropiate gencomp command.
(For now there are only 2)
"""
primary_command = command_line_args[0]
if primary_command == "help" or primary_command == "--help":
print HELP_MSG
else:
help_option = "--help"
if len(command_line_args) > 1:
help_option = parse_custom_help(command_line_args[1:])
help_command = [primary_command, help_option]
try:
help_output = subprocess.check_output(help_command)
except subprocess.CalledProcessError:
print "Error:"
print "Your help option is not supported for this command."
sys.exit(1)
except:
print "Unknown command."
sys.exit(1)
options = parse_options(help_output.splitlines())
print generate_completion_function(options, primary_command)

def parse_custom_help(remaining_options):
"""
Parses the custom help option (e.g. '-h') and
returns it, returns '--help' if option is invalid.
"""

if len(remaining_options) != 2:
return "--help"
elif remaining_options[0] != '-c':
return "--help"
else:
return remaining_options[1]

if __name__ == "__main__":
if len(sys.argv) > 1:
determine_command_options(sys.argv[1:])
else:
print HELP_MSG