diff --git a/.isort.cfg b/.isort.cfg index 9ac98225..d63487e9 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,3 +1,3 @@ [settings] line_length=79 -known_third_party=bottle,click,lockfile,pytest,requests,semantic_version,serial,SCons +known_third_party=arrow,bottle,click,lockfile,pytest,requests,SCons,semantic_version,serial diff --git a/HISTORY.rst b/HISTORY.rst index c5bc17b6..f3d7a843 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,6 +14,9 @@ PlatformIO 3.0 - Recent and popular keywords - Featured libraries (today, week, month) +* Show detailed info about a library using `pio lib show `__ + command + (`issue #430 `_) * Added support for templated methods in ``*.ino to *.cpp`` convertor (`pull #858 `_) * Produce less noisy output when ``-s/--silent`` options are used for diff --git a/docs b/docs index 30877574..b7090644 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 308775747a663039b5210d94f05431b32f7114ae +Subproject commit b709064430b0697353a54b55920ef49aa1062d6d diff --git a/platformio/__init__.py b/platformio/__init__.py index b5eb950a..4d00bb3c 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -14,7 +14,7 @@ import sys -VERSION = (3, 3, "0a1") +VERSION = (3, 3, "0a2") __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" diff --git a/platformio/commands/lib.py b/platformio/commands/lib.py index 81f6c670..1ca3a939 100644 --- a/platformio/commands/lib.py +++ b/platformio/commands/lib.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-branches, too-many-locals + import json -from datetime import datetime from os.path import join from time import sleep from urllib import quote +import arrow import click from platformio import exception, util @@ -46,7 +48,7 @@ from platformio.util import get_api_result @click.pass_context def cli(ctx, **options): # skip commands that don't need storage folder - if ctx.invoked_subcommand in ("search", "register", "stats") or \ + if ctx.invoked_subcommand in ("search", "show", "register", "stats") or \ (len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")): return storage_dir = options['storage_dir'] @@ -158,7 +160,7 @@ def echo_liblist_item(item): description=description)) -@cli.command("search", short_help="Search for library") +@cli.command("search", short_help="Search for a library") @click.argument("query", required=False, nargs=-1) @click.option("--json-output", is_flag=True) @click.option("--page", type=click.INT, default=1) @@ -247,8 +249,7 @@ def lib_list(lm, json_output): items = lm.get_installed() if json_output: - click.echo(json.dumps(items)) - return + return click.echo(json.dumps(items)) if not items: return @@ -260,28 +261,40 @@ def lib_list(lm, json_output): echo_liblist_item(item) -@cli.command("show", short_help="Show details about installed library") -@click.pass_obj +@cli.command("show", short_help="Show detailed info about a library") @click.argument("library", metavar="[LIBRARY]") -def lib_show(lm, library): # pylint: disable=too-many-branches - name, requirements, url = lm.parse_pkg_name(library) - package_dir = lm.get_package_dir(name, requirements, url) - if not package_dir: - click.secho( - "%s @ %s is not installed" % (name, requirements or "*"), - fg="yellow") - return +@click.option("--json-output", is_flag=True) +def lib_show(library, json_output): + lm = LibraryManager() + name, requirements, _ = lm.parse_pkg_name(library) + lib_id = lm.get_pkg_id_by_name( + name, requirements, silent=json_output, interactive=not json_output) + lib = get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") + if json_output: + return click.echo(json.dumps(lib)) - manifest = lm.load_manifest(package_dir) - - click.secho(manifest['name'], fg="cyan") - click.echo("=" * len(manifest['name'])) - if "description" in manifest: - click.echo(manifest['description']) + click.secho(lib['name'], fg="cyan") + click.echo("=" * len(lib['name'])) + click.echo(lib['description']) click.echo() + click.echo("Version: %s, released %s" % + (lib['version']['name'], + arrow.get(lib['version']['released']).humanize())) + click.echo("Registry ID: %d" % lib['id']) + click.echo("Manifest: %s" % lib['confurl']) + for key in ("homepage", "repository", "license"): + if key not in lib or not lib[key]: + continue + if isinstance(lib[key], list): + click.echo("%s: %s" % (key.title(), ", ".join(lib[key]))) + else: + click.echo("%s: %s" % (key.title(), lib[key])) + + blocks = [] + _authors = [] - for author in manifest.get("authors", []): + for author in lib.get("authors", []): _data = [] for key in ("name", "email", "url", "maintainer"): if not author[key]: @@ -294,19 +307,33 @@ def lib_show(lm, library): # pylint: disable=too-many-branches _data.append(author[key]) _authors.append(" ".join(_data)) if _authors: - click.echo("Authors: %s" % ", ".join(_authors)) + blocks.append(("Authors", _authors)) - 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: %s" % (key.title(), manifest[key])) + blocks.append(("Keywords", lib['keywords'])) + if lib['frameworks']: + blocks.append(("Compatible Frameworks", lib['frameworks'])) + if lib['platforms']: + blocks.append(("Compatible Platforms", lib['platforms'])) + blocks.append(("Headers", lib['headers'])) + blocks.append(("Examples", lib['examples'])) + blocks.append(("Versions", [ + "%s, released %s" % (v['name'], arrow.get(v['released']).humanize()) + for v in lib['versions'] + ])) + blocks.append(("Unique Downloads", [ + "Today: %s" % lib['dlstats']['day'], "Week: %s" % + lib['dlstats']['week'], "Month: %s" % lib['dlstats']['month'] + ])) + + for (title, rows) in blocks: + click.echo() + click.secho(title, bold=True) + click.echo("-" * len(title)) + for row in rows: + click.echo(row) -@cli.command("register", short_help="Register new library") +@cli.command("register", short_help="Register a new library") @click.argument("config_url") def lib_register(config_url): if (not config_url.startswith("http://") and @@ -353,8 +380,7 @@ def lib_stats(json_output): name=click.style( item['name'], fg="cyan"), date=str( - datetime.strptime(item['date'].replace("Z", "UTC"), - "%Y-%m-%dT%H:%M:%S%Z") + arrow.get(item['date']).humanize() if "date" in item else ""), url=click.style( "http://platformio.org/lib/show/%s/%s" % (item[ diff --git a/platformio/managers/lib.py b/platformio/managers/lib.py index 94241a8d..8008c753 100644 --- a/platformio/managers/lib.py +++ b/platformio/managers/lib.py @@ -19,6 +19,7 @@ import os from hashlib import md5 from os.path import dirname, join +import arrow import click import semantic_version @@ -129,13 +130,8 @@ class LibraryManager(BasePkgManager): def max_satisfying_repo_version(versions, requirements=None): def _cmp_dates(datestr1, datestr2): - from datetime import datetime - assert "T" in datestr1 and "T" in datestr2 - dateformat = "%Y-%m-%d %H:%M:%S" - date1 = datetime.strptime(datestr1[:-1].replace("T", " "), - dateformat) - date2 = datetime.strptime(datestr2[:-1].replace("T", " "), - dateformat) + date1 = arrow.get(datestr1) + date2 = arrow.get(datestr2) if date1 == date2: return 0 return -1 if date1 < date2 else 1 @@ -150,7 +146,7 @@ class LibraryManager(BasePkgManager): for v in versions: specver = None try: - specver = semantic_version.Version(v['version'], partial=True) + specver = semantic_version.Version(v['name'], partial=True) except ValueError: pass @@ -158,30 +154,30 @@ class LibraryManager(BasePkgManager): if not specver or specver not in reqspec: continue if not item or semantic_version.Version( - item['version'], partial=True) < specver: + item['name'], partial=True) < specver: item = v elif requirements: - if requirements == v['version']: + if requirements == v['name']: return v else: - if not item or _cmp_dates(item['date'], v['date']) == -1: + if not item or _cmp_dates(item['released'], + v['released']) == -1: item = v return item def get_latest_repo_version(self, name, requirements): item = self.max_satisfying_repo_version( util.get_api_result( - "/lib/versions/%d" % self._get_pkg_id_by_name(name, - requirements), - cache_valid="1h"), + "/lib/info/%d" % self.get_pkg_id_by_name(name, requirements), + cache_valid="1d")['versions'], requirements) - return item['version'] if item else None + return item['name'] if item else None - def _get_pkg_id_by_name(self, - name, - requirements, - silent=False, - interactive=False): + def get_pkg_id_by_name(self, + name, + requirements, + silent=False, + interactive=False): if name.startswith("id="): return int(name[3:]) # try to find ID from installed packages @@ -222,7 +218,7 @@ class LibraryManager(BasePkgManager): try: if not _url: - _name = "id=%d" % self._get_pkg_id_by_name( + _name = "id=%d" % self.get_pkg_id_by_name( _name, _requirements, silent=silent, @@ -299,30 +295,34 @@ class LibraryManager(BasePkgManager): if result['total'] == 1: lib_info = result['items'][0] elif result['total'] > 1: - click.secho( - "Conflict: More than one library has been found " - "by request %s:" % json.dumps(filters), - fg="red", - err=True) - commands.lib.echo_liblist_header() - for item in result['items']: - commands.lib.echo_liblist_item(item) - - if not interactive: - click.secho( - "Automatically chose the first available library " - "(use `--interactive` option to make a choice)", - fg="yellow", - err=True) + if silent and not interactive: lib_info = result['items'][0] else: - deplib_id = click.prompt( - "Please choose library ID", - type=click.Choice([str(i['id']) for i in result['items']])) + click.secho( + "Conflict: More than one library has been found " + "by request %s:" % json.dumps(filters), + fg="red", + err=True) + commands.lib.echo_liblist_header() for item in result['items']: - if item['id'] == int(deplib_id): - lib_info = item - break + commands.lib.echo_liblist_item(item) + + if not interactive: + click.secho( + "Automatically chose the first available library " + "(use `--interactive` option to make a choice)", + fg="yellow", + err=True) + lib_info = result['items'][0] + else: + deplib_id = click.prompt( + "Please choose library ID", + type=click.Choice( + [str(i['id']) for i in result['items']])) + for item in result['items']: + if item['id'] == int(deplib_id): + lib_info = item + break if not lib_info: if filters.keys() == ["name"]: diff --git a/setup.py b/setup.py index 4b71930f..ae4ad614 100644 --- a/setup.py +++ b/setup.py @@ -18,13 +18,14 @@ from platformio import (__author__, __description__, __email__, __license__, __title__, __url__, __version__) install_requires = [ + "arrow<1", "bottle<0.13", "click>=5,<6", - "lockfile>=0.9.1,<0.13", - "requests>=2.4.0,<3", - "semantic_version>=2.5.0", "colorama", - "pyserial>=3,<4" + "lockfile>=0.9.1,<0.13", + "pyserial>=3,<4", + "requests>=2.4.0,<3", + "semantic_version>=2.5.0" ] setup(