Skip to content

Commit

Permalink
Completer py api (xonsh#4018)
Browse files Browse the repository at this point in the history
* feat: add index to sqlite-history table

* feat: support style argument to RichCompletion object

this will enable setting style for ptk completers

* feat: add xonsh cli/argparser utils

* feat: add python api for adding/removing completers

fixes xonsh#3972

* fix: mypy error

* docs: add api doc

* fix: circular imports

* docs:

* fix: testing get-doc cross-platform

* Update xonsh/completers/_aliases.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/completers/_aliases.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* style: convert % to f-strings

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
  • Loading branch information
jnoortheen and gforsyth authored Dec 16, 2020
1 parent 9fe6ef4 commit a083d28
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 169 deletions.
11 changes: 11 additions & 0 deletions docs/api/cli_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. _xonsh_cli_utils:

***********************************************
ArgParser Utils (``xonsh.cli_utils``)
***********************************************

.. automodule:: xonsh.cli_utils
:members:
:undoc-members:
:private-members:

2 changes: 1 addition & 1 deletion docs/tutorial_completers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Registering a Completer
=======================

Once you have created a completion function, you can add it to the list of
active completers via the ``completer add`` command::
active completers via the ``completer add`` command or ``xonsh.completers.completer.add_one_completer`` function::

Usage:
completer add NAME FUNC [POS]
Expand Down
27 changes: 27 additions & 0 deletions news/completer-py-api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
**Added:**

* index for history's sqlite-DB
* support passing style from RichCompleter to PTK's Completer
* ``xonsh.cli_utils`` to create cli from functions easily.
* Python API for completer command with ``xonsh.completer`` module functions.


**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
35 changes: 35 additions & 0 deletions tests/test_cli_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Test module xonsh/cli_utils.py"""
from xonsh import cli_utils


def func_with_doc(param: str, multi: str) -> str:
"""func doc
multi-line
Parameters
----------
param
param doc
multi
param doc
multi line
Returns
-------
str
return doc
"""
return param + multi


def test_get_doc_param():
assert cli_utils.get_doc(func_with_doc).splitlines() == [
"func doc",
"multi-line",
]
assert cli_utils.get_doc(func_with_doc, "param").splitlines() == [
"param doc",
]
assert cli_utils.get_doc(func_with_doc, "multi").splitlines() == [
"param doc",
" multi line",
]
2 changes: 2 additions & 0 deletions xonsh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

xontribs_meta = __amalgam__
_sys.modules["xonsh.xontribs_meta"] = __amalgam__
cli_utils = __amalgam__
_sys.modules["xonsh.cli_utils"] = __amalgam__
contexts = __amalgam__
_sys.modules["xonsh.contexts"] = __amalgam__
lazyasd = __amalgam__
Expand Down
108 changes: 108 additions & 0 deletions xonsh/cli_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
small functions to create argparser CLI from functions.
"""

import argparse as ap
import os
import typing as tp


def _get_func_doc(doc: str) -> str:
lines = doc.splitlines()
if "Parameters" in lines:
idx = lines.index("Parameters")
lines = lines[:idx]
return os.linesep.join(lines)


def _from_index_of(container: tp.Sequence[str], key: str):
if key in container:
idx = container.index(key)
if idx + 1 < len(container):
return container[idx + 1 :]
return []


def _get_param_doc(doc: str, param: str) -> str:
lines = tuple(doc.splitlines())
if "Parameters" not in lines:
return ""

par_doc = []
for lin in _from_index_of(lines, param):
if lin and not lin.startswith(" "):
break
par_doc.append(lin)
return os.linesep.join(par_doc).strip()


def get_doc(func: tp.Callable, parameter: str = None):
"""Parse the function docstring and return its help content
Parameters
----------
func
a callable object that holds docstring
parameter
name of the function parameter to parse doc for
Returns
-------
str
doc of the parameter/function
"""
import inspect

doc = inspect.getdoc(func) or ""
if parameter:
return _get_param_doc(doc, parameter)
else:
return _get_func_doc(doc)


_FUNC_NAME = "_func_"


def make_parser(
func: tp.Callable,
subparser: ap._SubParsersAction = None,
params: tp.Dict[str, tp.Dict[str, tp.Any]] = None,
**kwargs
) -> "ap.ArgumentParser":
"""A bare-bones argparse builder from functions"""

doc = get_doc(func)
kwargs.setdefault("formatter_class", ap.RawTextHelpFormatter)
if subparser is None:
kwargs.setdefault("description", doc)
parser = ap.ArgumentParser(**kwargs)
parser.set_defaults(
**{_FUNC_NAME: lambda stdout: parser.print_help(file=stdout)}
)
return parser
else:
parser = subparser.add_parser(
kwargs.pop("prog", func.__name__),
help=doc,
**kwargs,
)
parser.set_defaults(**{_FUNC_NAME: func})

if params:
for par, args in params.items():
args.setdefault("help", get_doc(func, par))
parser.add_argument(par, **args)

return parser


def dispatch(**ns):
"""call the sub-command selected by user"""
import inspect

func = ns[_FUNC_NAME]
sign = inspect.signature(func)
kwargs = {}
for name, param in sign.parameters.items():
kwargs[name] = ns[name]
return func(**kwargs)
Loading

0 comments on commit a083d28

Please sign in to comment.