PlatformIO Library Manager 3.0 // Resolve #588 , Resolve #414, Resolve #498, Resolve #475, Resolve #410, Resolve #461, Resolve #361, Resolve #507, Resolve #554

This commit is contained in:
Ivan Kravets
2016-08-02 19:10:29 +03:00
parent 5b5a63cb5f
commit 758396c9ea
34 changed files with 1185 additions and 700 deletions

View File

@@ -1,4 +1,4 @@
# Copyright 2014-2016 Ivan Kravets <me@ikravets.com>
# Copyright 2014-present Ivan Kravets <me@ikravets.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,51 +13,136 @@
# limitations under the License.
import json
from os.path import join
import click
from platformio import app, exception
from platformio.libmanager import LibraryManager
from platformio import app, exception, util
from platformio.managers.lib import LibraryManager
from platformio.util import get_api_result
@click.group(short_help="Library Manager")
@click.option(
"-g",
"--global",
is_flag=True,
help="Manager global PlatformIO"
" library storage `%s`" % join(util.get_home_dir(), "lib"))
@click.option(
"-d",
"--storage-dir",
default=None,
type=click.Path(
exists=True,
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True),
help="Manage custom library storage")
@click.pass_context
def cli(ctx, **options):
# skip commands that don't need storage folder
if ctx.invoked_subcommand in ("search", "register") or \
(len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")):
return
storage_dir = options['storage_dir']
if not storage_dir and options['global']:
storage_dir = join(util.get_home_dir(), "lib")
if not storage_dir and not util.is_platformio_project():
raise exception.PlatformioException(
"The `%s` is not a PlatformIO project.\nTo manage libraries "
"in the global storage `%s`, please use "
"`platformio lib --global %s` instead." %
(util.get_project_dir(), join(util.get_home_dir(), "lib"),
ctx.invoked_subcommand))
ctx.obj = LibraryManager(storage_dir)
click.echo("Library Storage: " + storage_dir)
@cli.command("install", short_help="Install library")
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
@click.option(
"--save",
is_flag=True,
help="Save installed libraries into the project's platformio.ini "
"library dependencies")
@click.option(
"-q", "--quiet", is_flag=True, help="Suppress progress reporting")
@click.pass_obj
def lib_install(lm, libraries, save, quiet): # pylint: disable=unused-argument
# @TODO "save" option
for library in libraries:
lm.install(library, quiet=quiet)
@cli.command("uninstall", short_help="Uninstall libraries")
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
@click.pass_obj
def lib_uninstall(lm, libraries):
for library in libraries:
lm.uninstall(library)
@cli.command("update", short_help="Update installed libraries")
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
@click.option(
"-c",
"--only-check",
is_flag=True,
help="Do not update, only check for new version")
@click.pass_obj
def lib_update(lm, libraries, only_check):
if not libraries:
libraries = [str(m.get("id", m['name'])) for m in lm.get_installed()]
for library in libraries:
lm.update(library, only_check=only_check)
#######
LIBLIST_TPL = ("[{id:^14}] {name:<25} {compatibility:<30} "
"\"{authornames}\": {description}")
def echo_liblist_header():
click.echo(LIBLIST_TPL.format(
id=click.style("ID", fg="green"),
name=click.style("Name", fg="cyan"),
compatibility=click.style("Compatibility", fg="yellow"),
authornames="Authors",
description="Description"
))
click.echo(
LIBLIST_TPL.format(
id=click.style(
"ID", fg="green"),
name=click.style(
"Name", fg="cyan"),
compatibility=click.style(
"Compatibility", fg="yellow"),
authornames="Authors",
description="Description"))
terminal_width, _ = click.get_terminal_size()
click.echo("-" * terminal_width)
def echo_liblist_item(item):
click.echo(LIBLIST_TPL.format(
id=click.style(str(item['id']), fg="green"),
name=click.style(item['name'], fg="cyan"),
compatibility=click.style(
", ".join(item['frameworks'] + item['platforms']),
fg="yellow"
),
authornames=", ".join(item['authornames']),
description=item['description']
))
@click.group(short_help="Library Manager")
def cli():
pass
click.echo(
LIBLIST_TPL.format(
id=click.style(
str(item.get("id", "VCS")), fg="green"),
name=click.style(
item['name'], fg="cyan"),
compatibility=click.style(
", ".join(
item.get("frameworks", ["-"]) + item.get("platforms", [])),
fg="yellow"),
authornames=", ".join(item.get("authornames", ["Unknown"])),
description=item.get("description", item.get("url", ""))) +
(" | @" + click.style(
item['version'], fg="yellow") if "version" in item else ""))
@cli.command("search", short_help="Search for library")
@click.option("--json-output", is_flag=True)
@click.option("--page", type=click.INT, default=1)
@click.option("-n", "--name", multiple=True)
@click.option("-a", "--author", multiple=True)
@click.option("-k", "--keyword", multiple=True)
@click.option("-f", "--framework", multiple=True)
@@ -73,8 +158,9 @@ def lib_search(query, json_output, page, **filters):
for value in values:
query.append('%s:"%s"' % (key, value))
result = get_api_result("/lib/search",
dict(query=" ".join(query), page=page))
result = get_api_result(
"/lib/search", dict(
query=" ".join(query), page=page))
if json_output:
click.echo(json.dumps(result))
@@ -84,17 +170,22 @@ def lib_search(query, json_output, page, **filters):
click.secho(
"Nothing has been found by your request\n"
"Try a less-specific search or use truncation (or wildcard) "
"operator", fg="yellow", nl=False)
"operator",
fg="yellow",
nl=False)
click.secho(" *", fg="green")
click.secho("For example: DS*, PCA*, DHT* and etc.\n", fg="yellow")
click.echo("For more examples and advanced search syntax, "
"please use documentation:")
click.secho("http://docs.platformio.org"
"/en/latest/userguide/lib/cmd_search.html\n", fg="cyan")
click.secho(
"http://docs.platformio.org"
"/en/latest/userguide/lib/cmd_search.html\n",
fg="cyan")
return
click.secho("Found %d libraries:\n" % result['total'],
fg="green" if result['total'] else "yellow")
click.secho(
"Found %d libraries:\n" % result['total'],
fg="green" if result['total'] else "yellow")
if result['total']:
echo_liblist_header()
@@ -111,94 +202,17 @@ def lib_search(query, json_output, page, **filters):
click.confirm("Show next libraries?")):
result = get_api_result(
"/lib/search",
dict(query=" ".join(query), page=int(result['page']) + 1)
)
dict(
query=" ".join(query), page=int(result['page']) + 1))
else:
break
@cli.command("install", short_help="Install library")
@click.argument("libid", type=click.INT, nargs=-1, metavar="[LIBRARY_ID]")
@click.option("-v", "--version")
@click.pass_context
def lib_install(ctx, libid, version):
lm = LibraryManager()
for id_ in libid:
click.echo(
"Installing library [ %s ]:" % click.style(str(id_), fg="green"))
try:
if not lm.install(id_, version):
continue
info = lm.get_info(id_)
click.secho(
"The library #%s '%s' has been successfully installed!"
% (str(id_), info['name']), fg="green")
if "dependencies" in info:
click.secho("Installing dependencies:", fg="yellow")
_dependencies = info['dependencies']
if not isinstance(_dependencies, list):
_dependencies = [_dependencies]
for item in _dependencies:
try:
lib_install_dependency(ctx, item)
except AssertionError:
raise exception.LibInstallDependencyError(str(item))
except exception.LibAlreadyInstalled:
click.secho("Already installed", fg="yellow")
def lib_install_dependency(ctx, data):
assert isinstance(data, dict)
query = []
for key in data:
if key in ("authors", "frameworks", "platforms", "keywords"):
values = data[key]
if not isinstance(values, list):
values = [v.strip() for v in values.split(",") if v]
for value in values:
query.append('%s:"%s"' % (key[:-1], value))
elif isinstance(data[key], basestring):
query.append('+"%s"' % data[key])
result = get_api_result("/lib/search", dict(query=" ".join(query)))
assert result['total'] > 0
if result['total'] == 1 or not app.get_setting("enable_prompts"):
ctx.invoke(lib_install, libid=[result['items'][0]['id']])
else:
click.secho(
"Conflict: More than one dependent libraries have been found "
"by request %s:" % json.dumps(data), fg="red")
echo_liblist_header()
for item in result['items']:
echo_liblist_item(item)
deplib_id = click.prompt(
"Please choose one dependent library ID",
type=click.Choice([str(i['id']) for i in result['items']]))
ctx.invoke(lib_install, libid=[int(deplib_id)])
@cli.command("uninstall", short_help="Uninstall libraries")
@click.argument("libid", type=click.INT, nargs=-1)
def lib_uninstall(libid):
lm = LibraryManager()
for id_ in libid:
info = lm.get_info(id_)
if lm.uninstall(id_):
click.secho("The library #%s '%s' has been successfully "
"uninstalled!" % (str(id_), info['name']), fg="green")
@cli.command("list", short_help="List installed libraries")
@click.option("--json-output", is_flag=True)
def lib_list(json_output):
lm = LibraryManager()
items = lm.get_installed().values()
@click.pass_obj
def lib_list(lm, json_output):
items = lm.get_installed()
if json_output:
click.echo(json.dumps(items))
@@ -208,21 +222,34 @@ def lib_list(json_output):
return
echo_liblist_header()
for item in sorted(items, key=lambda i: i['id']):
item['authornames'] = [i['name'] for i in item['authors']]
for item in sorted(items, key=lambda i: i['name']):
if "authors" in item:
item['authornames'] = [i['name'] for i in item['authors']]
echo_liblist_item(item)
@cli.command("show", short_help="Show details about installed library")
@click.argument("libid", type=click.INT)
def lib_show(libid):
lm = LibraryManager()
info = lm.get_info(libid)
click.secho(info['name'], fg="cyan")
click.echo("-" * len(info['name']))
@click.pass_obj
@click.argument("library", metavar="[LIBRARY]")
def lib_show(lm, library): # pylint: disable=too-many-branches
name, requirements, url = lm.parse_pkg_name(library)
installed_dir = lm.get_installed_dir(name, requirements, url)
if not installed_dir:
click.secho(
"%s @ %s is not installed" % (name, requirements or "*"),
fg="yellow")
return
manifest = lm.load_manifest(installed_dir)
click.secho(manifest['name'], fg="cyan")
click.echo("=" * len(manifest['name']))
if "description" in manifest:
click.echo(manifest['description'])
click.echo()
_authors = []
for author in info['authors']:
for author in manifest.get("authors", []):
_data = []
for key in ("name", "email", "url", "maintainer"):
if not author[key]:
@@ -234,61 +261,29 @@ def lib_show(libid):
else:
_data.append(author[key])
_authors.append(" ".join(_data))
click.echo("Authors: %s" % ", ".join(_authors))
if _authors:
click.echo("Authors: %s" % ", ".join(_authors))
click.echo("Keywords: %s" % ", ".join(info['keywords']))
if "frameworks" in info:
click.echo("Frameworks: %s" % ", ".join(info['frameworks']))
if "platforms" in info:
click.echo("Platforms: %s" % ", ".join(info['platforms']))
click.echo("Version: %s" % info['version'])
click.echo()
click.echo(info['description'])
click.echo()
@cli.command("update", short_help="Update installed libraries")
@click.argument("libid", type=click.INT, nargs=-1, required=False,
metavar="[LIBRARY_ID]")
@click.pass_context
def lib_update(ctx, libid):
lm = LibraryManager()
for id_, latest_version in (lm.get_latest_versions() or {}).items():
if libid and int(id_) not in libid:
continue
info = lm.get_info(int(id_))
click.echo("Updating [ %s ] %s library:" % (
click.style(id_, fg="yellow"),
click.style(info['name'], fg="cyan")))
current_version = info['version']
if latest_version is None:
click.secho("Unknown library", fg="red")
continue
click.echo("Versions: Current=%s, Latest=%s \t " % (
current_version, latest_version), nl=False)
if current_version == latest_version:
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
for key in ("keywords", "frameworks", "platforms", "license", "url",
"version"):
if key not in manifest:
continue
if isinstance(manifest[key], list):
click.echo("%s: %s" % (key.title(), ", ".join(manifest[key])))
else:
click.echo("[%s]" % (click.style("Out-of-date", fg="red")))
ctx.invoke(lib_uninstall, libid=[int(id_)])
ctx.invoke(lib_install, libid=[int(id_)])
click.echo("%s: %s" % (key.title(), manifest[key]))
@cli.command("register", short_help="Register new library")
@click.argument("config_url")
def lib_register(config_url):
if (not config_url.startswith("http://") and not
config_url.startswith("https://")):
if (not config_url.startswith("http://") and
not config_url.startswith("https://")):
raise exception.InvalidLibConfURL(config_url)
result = get_api_result("/lib/register", data=dict(config_url=config_url))
if "message" in result and result['message']:
click.secho(result['message'], fg="green" if "successed" in result and
result['successed'] else "red")
click.secho(
result['message'],
fg="green"
if "successed" in result and result['successed'] else "red")