add module names completion for install (#103)
* add module names completion for install * sort completion suggestions * add instructions in the readme * switch to click 8 * update setup.py to click 8 * fix from rebase (missing bundle list) * try not to update bundles for shell completion
This commit is contained in:
parent
820dc0a7be
commit
f797cdd08e
5 changed files with 112 additions and 6 deletions
57
README.rst
57
README.rst
|
|
@ -211,6 +211,63 @@ The ``--version`` flag will tell you the current version of the
|
|||
|
||||
That's it!
|
||||
|
||||
|
||||
Library Name Autocomplete
|
||||
-------------------------
|
||||
|
||||
When enabled, circup will autocomplete library names, simliar to other command line tools.
|
||||
|
||||
For example:
|
||||
|
||||
``circup install n`` + tab -``circup install neopixel`` (+tab: offers ``neopixel`` and ``neopixel_spi`` completions)
|
||||
|
||||
``circup install a`` + tab -``circup install adafruit\_`` + m a g + tab -``circup install adafruit_magtag``
|
||||
|
||||
How to Activate Library Name Autocomplete
|
||||
-----------------------------------------
|
||||
|
||||
In order to activate shell completion, you need to inform your shell that completion is available for your script. Any Click application automatically provides support for that.
|
||||
|
||||
For Bash, add this to ~/.bashrc::
|
||||
|
||||
eval "$(_CIRCUP_COMPLETE=bash_source circup)"
|
||||
|
||||
For Zsh, add this to ~/.zshrc::
|
||||
|
||||
eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
|
||||
|
||||
For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
|
||||
|
||||
eval (env _CIRCUP_COMPLETE=fish_source circup)
|
||||
|
||||
Open a new shell to enable completion. Or run the eval command directly in your current shell to enable it temporarily.
|
||||
### Activation Script
|
||||
|
||||
The above eval examples will invoke your application every time a shell is started. This may slow down shell startup time significantly.
|
||||
|
||||
Alternatively, export the generated completion code as a static script to be executed. You can ship this file with your builds; tools like Git do this. At least Zsh will also cache the results of completion files, but not eval scripts.
|
||||
|
||||
For Bash::
|
||||
|
||||
_CIRCUP_COMPLETE=bash_source circup circup-complete.sh
|
||||
|
||||
For Zsh::
|
||||
|
||||
_CIRCUP_COMPLETE=zsh_source circup circup-complete.sh
|
||||
|
||||
For Fish::
|
||||
|
||||
_CIRCUP_COMPLETE=fish_source circup circup-complete.sh
|
||||
|
||||
In .bashrc or .zshrc, source the script instead of the eval command::
|
||||
|
||||
. /path/to/circup-complete.sh
|
||||
|
||||
For Fish, add the file to the completions directory::
|
||||
|
||||
_CIRCUP_COMPLETE=fish_source circup ~/.config/fish/completions/circup-complete.fish
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
If you find a bug, or you want to suggest an enhancement or new feature
|
||||
|
|
|
|||
25
circup.py
25
circup.py
|
|
@ -385,10 +385,25 @@ def clean_library_name(assumed_library_name):
|
|||
return assumed_library_name
|
||||
|
||||
|
||||
def completion_for_install(ctx, param, incomplete):
|
||||
"""
|
||||
Returns the list of available modules for the command line tab-completion
|
||||
with the ``circup install`` command.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
available_modules = get_bundle_versions(get_bundles_list(), avoid_download=True)
|
||||
module_names = {m.replace(".py", "") for m in available_modules}
|
||||
if incomplete:
|
||||
module_names = [name for name in module_names if name.startswith(incomplete)]
|
||||
return sorted(module_names)
|
||||
|
||||
|
||||
def ensure_latest_bundle(bundle):
|
||||
"""
|
||||
Ensure that there's a copy of the latest library bundle available so circup
|
||||
can check the metadata contained therein.
|
||||
|
||||
:param Bundle bundle: the target Bundle object.
|
||||
"""
|
||||
logger.info("Checking library updates for %s.", bundle.key)
|
||||
tag = bundle.latest_tag
|
||||
|
|
@ -639,19 +654,21 @@ def get_bundle(bundle, tag):
|
|||
click.echo("\nOK\n")
|
||||
|
||||
|
||||
def get_bundle_versions(bundles_list):
|
||||
def get_bundle_versions(bundles_list, avoid_download=False):
|
||||
"""
|
||||
Returns a dictionary of metadata from modules in the latest known release
|
||||
of the library bundle. Uses the Python version (rather than the compiled
|
||||
version) of the library modules.
|
||||
|
||||
:param Bundle bundles_list: List of supported bundles as Bundle objects.
|
||||
:param bool avoid_download: if True, download the bundle only if missing.
|
||||
:return: A dictionary of metadata about the modules available in the
|
||||
library bundle.
|
||||
"""
|
||||
all_the_modules = dict()
|
||||
for bundle in bundles_list:
|
||||
ensure_latest_bundle(bundle)
|
||||
if not avoid_download or not os.path.isdir(bundle.lib_dir("py")):
|
||||
ensure_latest_bundle(bundle)
|
||||
path = bundle.lib_dir("py")
|
||||
path_modules = get_modules(path)
|
||||
for name, module in path_modules.items():
|
||||
|
|
@ -1102,7 +1119,9 @@ def list(ctx): # pragma: no cover
|
|||
|
||||
|
||||
@main.command()
|
||||
@click.argument("modules", required=False, nargs=-1)
|
||||
@click.argument(
|
||||
"modules", required=False, nargs=-1, shell_complete=completion_for_install
|
||||
)
|
||||
@click.option("--py", is_flag=True)
|
||||
@click.option("-r", "--requirement")
|
||||
@click.pass_context
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ black==19.3b0
|
|||
bleach==3.3.0
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
Click>=7.0
|
||||
Click>=8.0
|
||||
coverage==4.5.4
|
||||
docutils==0.15.2
|
||||
idna==2.8
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -24,7 +24,7 @@ with open(path.join(here, "README.rst"), encoding="utf-8") as f:
|
|||
|
||||
install_requires = [
|
||||
"semver~=2.13",
|
||||
"Click>=7.0",
|
||||
"Click>=8.0",
|
||||
"appdirs>=1.4.3",
|
||||
"requests>=2.22.0",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -564,20 +564,50 @@ def test_find_modules_goes_bang():
|
|||
def test_get_bundle_versions():
|
||||
"""
|
||||
Ensure get_modules is called with the path for the library bundle.
|
||||
Ensure ensure_latest_bundle is called even if lib_dir exists.
|
||||
"""
|
||||
with mock.patch("circup.ensure_latest_bundle"), mock.patch(
|
||||
with mock.patch("circup.ensure_latest_bundle") as mock_elb, mock.patch(
|
||||
"circup.get_modules", return_value={"ok": {"name": "ok"}}
|
||||
) as mock_gm, mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch(
|
||||
"circup.Bundle.lib_dir", return_value="foo/bar/lib"
|
||||
), mock.patch(
|
||||
"circup.os.path.isdir", return_value=True
|
||||
):
|
||||
bundle = circup.Bundle(TEST_BUNDLE_NAME)
|
||||
bundles_list = [bundle]
|
||||
assert circup.get_bundle_versions(bundles_list) == {
|
||||
"ok": {"name": "ok", "bundle": bundle}
|
||||
}
|
||||
mock_elb.assert_called_once_with(bundle)
|
||||
mock_gm.assert_called_once_with("foo/bar/lib")
|
||||
|
||||
|
||||
def test_get_bundle_versions_avoid_download():
|
||||
"""
|
||||
When avoid_download is True and lib_dir exists, don't ensure_latest_bundle.
|
||||
Testing both cases: lib_dir exists and lib_dir doesn't exists.
|
||||
"""
|
||||
with mock.patch("circup.ensure_latest_bundle") as mock_elb, mock.patch(
|
||||
"circup.get_modules", return_value={"ok": {"name": "ok"}}
|
||||
) as mock_gm, mock.patch("circup.CPY_VERSION", "4.1.2"), mock.patch(
|
||||
"circup.Bundle.lib_dir", return_value="foo/bar/lib"
|
||||
):
|
||||
bundle = circup.Bundle(TEST_BUNDLE_NAME)
|
||||
bundles_list = [bundle]
|
||||
with mock.patch("circup.os.path.isdir", return_value=True):
|
||||
assert circup.get_bundle_versions(bundles_list, avoid_download=True) == {
|
||||
"ok": {"name": "ok", "bundle": bundle}
|
||||
}
|
||||
assert mock_elb.call_count == 0
|
||||
mock_gm.assert_called_once_with("foo/bar/lib")
|
||||
with mock.patch("circup.os.path.isdir", return_value=False):
|
||||
assert circup.get_bundle_versions(bundles_list, avoid_download=True) == {
|
||||
"ok": {"name": "ok", "bundle": bundle}
|
||||
}
|
||||
mock_elb.assert_called_once_with(bundle)
|
||||
mock_gm.assert_called_with("foo/bar/lib")
|
||||
|
||||
|
||||
def test_get_circuitpython_version():
|
||||
"""
|
||||
Given valid content of a boot_out.txt file on a connected device, return
|
||||
|
|
|
|||
Loading…
Reference in a new issue