Merge branch 'zajaczajac-master'

This commit is contained in:
Anthony Scopatz 2015-06-19 14:30:22 +03:00
commit 693c638faf
4 changed files with 70 additions and 1 deletions

3
.gitignore vendored
View file

@ -28,3 +28,6 @@ pip-selfcheck.json
bin/
lib/
include/
# Mac
.DS_Store

BIN
tests/man1/yes.1.gz Normal file

Binary file not shown.

20
tests/test_man.py Normal file
View file

@ -0,0 +1,20 @@
import os
import platform
import nose
from nose.tools import assert_true
from xonsh.completer import ManCompleter
os.environ['MANPATH'] = os.path.dirname(os.path.abspath(__file__))
def test_man_completion():
if (platform.system() != 'Windows'):
man_completer = ManCompleter()
completions = man_completer.option_complete('--', 'yes')
assert_true('--version' in completions)
assert_true('--help' in completions)
if __name__ == '__main__':
nose.runmodule()

View file

@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
import os
import re
import builtins
import pickle
import subprocess
import sys
@ -43,6 +44,7 @@ class Completer(object):
self._alias_checksum = None
self._path_mtime = -1
self._cmds_cache = frozenset()
self._man_completer = ManCompleter()
try:
# FIXME this could be threaded for faster startup times
self._load_bash_complete_funcs()
@ -94,6 +96,8 @@ class Completer(object):
elif prefix.startswith('${') or prefix.startswith('@('):
# python mode explicitly
rtn = set()
elif prefix.startswith('-'):
return sorted(self._man_completer.option_complete(prefix, cmd))
elif cmd not in ctx:
if cmd == 'import' and begidx == len('import '):
# completing module to import
@ -136,7 +140,7 @@ class Completer(object):
return {s + space for s in self._all_commands() if s.startswith(cmd)}
def module_complete(self, prefix):
"""Completes a name of a module to import"""
"""Completes a name of a module to import."""
modules = set(sys.modules.keys())
return {s for s in modules if s.startswith(prefix)}
@ -298,3 +302,45 @@ class Completer(object):
allcmds |= set(builtins.aliases.keys())
self._cmds_cache = frozenset(allcmds)
return self._cmds_cache
OPTIONS_PATH = os.path.expanduser('~') + "/.xonsh_man_completions"
SCRAPE_RE = re.compile(r'^(?:\s*(?:-\w|--[a-z0-9-]+)[\s,])+', re.M)
INNER_OPTIONS_RE = re.compile(r'-\w|--[a-z0-9-]+')
class ManCompleter(object):
"""Helper class that loads completions derived from man pages."""
def __init__(self):
self._load_cached_options()
def __del__(self):
self._save_cached_options()
def option_complete(self, prefix, cmd):
"""Completes an option name, basing on content of man page."""
if cmd not in self._options.keys():
try:
manpage = subprocess.Popen(["man", cmd], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
# This is a trick to get rid of reverse line feeds
text = subprocess.check_output(["col", "-b"], stdin=manpage.stdout).decode('utf-8')
scraped_text = ' '.join(SCRAPE_RE.findall(text))
matches = INNER_OPTIONS_RE.findall(scraped_text)
self._options[cmd] = matches
except:
return set()
return {s for s in self._options[cmd] if s.startswith(prefix)}
def _load_cached_options(self):
"""Load options from file at startup."""
try:
with open(OPTIONS_PATH, 'rb') as f:
self._options = pickle.load(f)
except:
self._options = {}
def _save_cached_options(self):
"""Save completions to file."""
with open(OPTIONS_PATH, 'wb') as f:
pickle.dump(self._options, f)