diff --git a/HISTORY.rst b/HISTORY.rst index b16ba0ff..bf515ba2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -17,9 +17,11 @@ PlatformIO Core 5 * `pio pkg exec `_ - run command from package tool (`issue #4163 `_) * `pio pkg install `_ - install the project dependencies or custom packages + * `pio pkg list `__ - list installed packages * `pio pkg outdated `__ - check for project outdated packages - * `pio pkg update `__ - update the project dependencies or custom packages + * `pio pkg show `__ - show package information * `pio pkg uninstall `_ - uninstall the project dependencies or custom packages + * `pio pkg update `__ - update the project dependencies or custom packages - Added support for dependencies declared in a "tool" type package - Ignore files according to the patterns declared in ".gitignore" when using `pio package pack `__ command (`issue #4188 `_) diff --git a/docs b/docs index a04cdef3..af80042f 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit a04cdef33cd508420fd837a7baf09cebc738faad +Subproject commit af80042f8dba2a665d52c99559504f712e0060e1 diff --git a/platformio/commands/pkg.py b/platformio/commands/pkg.py index 0ec9f411..d9c1fcd1 100644 --- a/platformio/commands/pkg.py +++ b/platformio/commands/pkg.py @@ -20,6 +20,7 @@ from platformio.package.commands.list import package_list_cmd from platformio.package.commands.outdated import package_outdated_cmd from platformio.package.commands.pack import package_pack_cmd from platformio.package.commands.publish import package_publish_cmd +from platformio.package.commands.show import package_show_cmd from platformio.package.commands.uninstall import package_uninstall_cmd from platformio.package.commands.unpublish import package_unpublish_cmd from platformio.package.commands.update import package_update_cmd @@ -34,6 +35,7 @@ from platformio.package.commands.update import package_update_cmd package_outdated_cmd, package_pack_cmd, package_publish_cmd, + package_show_cmd, package_uninstall_cmd, package_unpublish_cmd, package_update_cmd, diff --git a/platformio/package/commands/show.py b/platformio/package/commands/show.py new file mode 100644 index 00000000..8f2a6578 --- /dev/null +++ b/platformio/package/commands/show.py @@ -0,0 +1,146 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from urllib.parse import quote + +import click +from tabulate import tabulate + +from platformio import fs, util +from platformio.clients.registry import RegistryClient +from platformio.exception import UserSideException +from platformio.package.manager._registry import PackageManageRegistryMixin +from platformio.package.meta import PackageSpec, PackageType + + +@click.command("show", short_help="Show package information") +@click.argument("spec", metavar="[/][@]") +@click.option( + "-t", + "--type", + "pkg_type", + type=click.Choice(list(PackageType.items().values())), + help="Package type", +) +def package_show_cmd(spec, pkg_type): + spec = PackageSpec(spec) + data = fetch_package_data(spec, pkg_type) + if not data: + raise UserSideException( + "Could not find '%s' package in the PlatormIO Registry" % spec.humanize() + ) + + type_plural = "libraries" if data["type"] == "library" else (data["type"] + "s") + click.echo() + click.echo( + "%s/%s" + % ( + click.style(data["owner"]["username"], fg="cyan"), + click.style(data["name"], fg="cyan", bold=True), + ) + ) + click.echo( + "%s • %s • %s • Published %s" + % ( + data["type"].capitalize(), + data["version"]["name"], + "Private" if data.get("private") else "Public", + util.parse_datetime(data["version"]["released_at"]).strftime("%c"), + ) + ) + + click.echo() + click.secho( + "https://registry.platformio.org/%s/%s/%s" + % (type_plural, data["owner"]["username"], quote(data["name"])), + fg="blue", + ) + + # Description + click.echo() + click.echo(data["description"]) + + # Extra info + click.echo() + fields = [ + ("homepage", "Homepage"), + ("repository_url", "Repository"), + ("license", "License"), + ("popularity_rank", "Popularity"), + ("stars_count", "Stars"), + ("examples_count", "Examples"), + ("version.unpacked_size", "Installed Size"), + ("dependents_count", "Used By"), + ("dependencies_count", "Dependencies"), + ("platforms", "Compatible Platforms"), + ("frameworks", "Compatible Frameworks"), + ("keywords", "Keywords"), + ] + extra = [] + for key, title in fields: + if "." in key: + k1, k2 = key.split(".") + value = data.get(k1, {}).get(k2) + else: + value = data.get(key) + if not value: + continue + if isinstance(value, list): + value = ", ".join(value) + elif key.endswith("_size"): + value = fs.humanize_file_size(value) + extra.append((title, value)) + click.echo(tabulate(extra)) + + # Versions + click.echo("") + table = tabulate( + [ + ( + version["name"], + fs.humanize_file_size(max(f["size"] for f in version["files"])), + util.parse_datetime(version["released_at"]), + ) + for version in data["versions"] + ], + headers=["Version", "Size", "Published"], + ) + click.echo(table) + click.echo("") + + +def fetch_package_data(spec, pkg_type=None): + assert isinstance(spec, PackageSpec) + client = RegistryClient() + if pkg_type and spec.owner and spec.name: + return client.get_package( + pkg_type, spec.owner, spec.name, version=spec.requirements + ) + filters = dict(names=spec.name.lower()) + if pkg_type: + filters["types"] = pkg_type + if spec.owner: + filters["owners"] = spec.owner.lower() + packages = client.list_packages(filters=filters)["items"] + if not packages: + return None + if len(packages) > 1: + PackageManageRegistryMixin.print_multi_package_issue(click.echo, packages, spec) + return None + return client.get_package( + packages[0]["type"], + packages[0]["owner"]["username"], + packages[0]["name"], + version=spec.requirements, + ) diff --git a/platformio/package/manager/_registry.py b/platformio/package/manager/_registry.py index 887728b3..715652bc 100644 --- a/platformio/package/manager/_registry.py +++ b/platformio/package/manager/_registry.py @@ -90,7 +90,7 @@ class PackageManageRegistryMixin(object): if not packages: raise UnknownPackageError(spec.humanize()) if len(packages) > 1: - self.print_multi_package_issue(packages, spec) + self.print_multi_package_issue(self.log.warning, packages, spec) package, version = self.find_best_registry_version(packages, spec) if not package or not version: @@ -164,12 +164,13 @@ class PackageManageRegistryMixin(object): if not packages: raise UnknownPackageError(spec.humanize()) if len(packages) > 1: - self.print_multi_package_issue(packages, spec) + self.print_multi_package_issue(self.log.warning, packages, spec) self.log.info("") return packages[0]["id"] - def print_multi_package_issue(self, packages, spec): - self.log.warning( + @staticmethod + def print_multi_package_issue(print_func, packages, spec): + print_func( click.style( "Warning! More than one package has been found by ", fg="yellow" ) @@ -178,14 +179,14 @@ class PackageManageRegistryMixin(object): ) for item in packages: - self.log.warning( + print_func( " - {owner}/{name}@{version}".format( owner=click.style(item["owner"]["username"], fg="cyan"), name=item["name"], version=item["version"]["name"], ) ) - self.log.warning( + print_func( click.style( "Please specify detailed REQUIREMENTS using package owner and version " "(shown above) to avoid name conflicts", diff --git a/tests/commands/pkg/test_show.py b/tests/commands/pkg/test_show.py new file mode 100644 index 00000000..979edbb3 --- /dev/null +++ b/tests/commands/pkg/test_show.py @@ -0,0 +1,103 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from platformio.exception import UserSideException +from platformio.package.commands.show import package_show_cmd + + +def test_spec_name(clirunner, validate_cliresult): + # library + result = clirunner.invoke( + package_show_cmd, + ["ArduinoJSON"], + ) + validate_cliresult(result) + assert "bblanchon/ArduinoJson" in result.output + assert "Library" in result.output + + # platform + result = clirunner.invoke( + package_show_cmd, + ["espressif32"], + ) + validate_cliresult(result) + assert "platformio/espressif32" in result.output + assert "Platform" in result.output + + # tool + result = clirunner.invoke( + package_show_cmd, + ["tool-jlink"], + ) + validate_cliresult(result) + assert "platformio/tool-jlink" in result.output + assert "tool" in result.output + + +def test_spec_owner(clirunner, validate_cliresult): + result = clirunner.invoke( + package_show_cmd, + ["bblanchon/ArduinoJSON"], + ) + validate_cliresult(result) + assert "bblanchon/ArduinoJson" in result.output + assert "Library" in result.output + + # test broken owner + result = clirunner.invoke( + package_show_cmd, + ["unknown/espressif32"], + ) + with pytest.raises(UserSideException, match="Could not find"): + raise result.exception + + +def test_complete_spec(clirunner, validate_cliresult): + result = clirunner.invoke( + package_show_cmd, + ["bblanchon/ArduinoJSON", "-t", "library"], + ) + validate_cliresult(result) + assert "bblanchon/ArduinoJson" in result.output + assert "Library" in result.output + + # tool + result = clirunner.invoke( + package_show_cmd, + ["platformio/tool-jlink", "-t", "tool"], + ) + validate_cliresult(result) + assert "platformio/tool-jlink" in result.output + assert "tool" in result.output + + +def test_name_conflict(clirunner): + result = clirunner.invoke( + package_show_cmd, + ["OneWire", "-t", "library"], + ) + assert "More than one package" in result.output + assert isinstance(result.exception, UserSideException) + + +def test_spec_version(clirunner, validate_cliresult): + result = clirunner.invoke( + package_show_cmd, + ["bblanchon/ArduinoJSON@5.13.4"], + ) + validate_cliresult(result) + assert "bblanchon/ArduinoJson" in result.output + assert "Library • 5.13.4" in result.output