# 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. # pylint: disable=too-many-branches, too-many-locals import json import time from os.path import isdir, join from urllib import quote import click from platformio import exception, util from platformio.managers.lib import LibraryManager, get_builtin_libs from platformio.util import get_api_result @click.group(short_help="Library Manager") @click.option( "-g", "--global", is_flag=True, help="Manage global PlatformIO library storage") @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): non_storage_cmds = ("search", "show", "register", "stats", "builtin") # skip commands that don't need storage folder if ctx.invoked_subcommand in non_storage_cmds or \ (len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")): return storage_dir = options['storage_dir'] if not storage_dir: if options['global']: storage_dir = join(util.get_home_dir(), "lib") elif util.is_platformio_project(): storage_dir = util.get_projectlibdeps_dir() elif util.is_ci(): storage_dir = join(util.get_home_dir(), "lib") click.secho( "Warning! Global library storage is used automatically. " "Please use `platformio lib --global %s` command to remove " "this warning." % ctx.invoked_subcommand, fg="yellow") elif util.is_platformio_project(storage_dir): with util.cd(storage_dir): storage_dir = util.get_projectlibdeps_dir() if not storage_dir and not util.is_platformio_project(): raise exception.NotGlobalLibDir(util.get_project_dir(), join(util.get_home_dir(), "lib"), ctx.invoked_subcommand) ctx.obj = LibraryManager(storage_dir) if "--json-output" not in ctx.args: 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( "-s", "--silent", is_flag=True, help="Suppress progress reporting") @click.option( "--interactive", is_flag=True, help="Allow to make a choice for all prompts") @click.option( "-f", "--force", is_flag=True, help="Reinstall/redownload library if exists") @click.pass_obj def lib_install(lm, libraries, silent, interactive, force): # @TODO: "save" option for library in libraries: lm.install( library, silent=silent, interactive=interactive, force=force) @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.option("--json-output", is_flag=True) @click.pass_obj def lib_update(lm, libraries, only_check, json_output): if not libraries: libraries = [manifest['__pkg_dir'] for manifest in lm.get_installed()] if only_check and json_output: result = [] for library in libraries: pkg_dir = library if isdir(library) else None requirements = None url = None if not pkg_dir: name, requirements, url = lm.parse_pkg_uri(library) pkg_dir = lm.get_package_dir(name, requirements, url) if not pkg_dir: continue latest = lm.outdated(pkg_dir, requirements) if not latest: continue manifest = lm.load_manifest(pkg_dir) manifest['versionLatest'] = latest result.append(manifest) return click.echo(json.dumps(result)) else: for library in libraries: lm.update(library, only_check=only_check) return True def print_lib_item(item): click.secho(item['name'], fg="cyan") click.echo("=" * len(item['name'])) if "id" in item: click.secho("#ID: %d" % item['id'], bold=True) if "description" in item or "url" in item: click.echo(item.get("description", item.get("url", ""))) click.echo() for key in ("version", "homepage", "license", "keywords"): if key not in item or not item[key]: continue if isinstance(item[key], list): click.echo("%s: %s" % (key.title(), ", ".join(item[key]))) else: click.echo("%s: %s" % (key.title(), item[key])) for key in ("frameworks", "platforms"): if key not in item: continue click.echo("Compatible %s: %s" % (key, ", ".join( [i['title'] if isinstance(i, dict) else i for i in item[key]]))) if "authors" in item or "authornames" in item: click.echo("Authors: %s" % ", ".join( item.get("authornames", [a.get("name", "") for a in item.get("authors", [])]))) if "__src_url" in item: click.secho("Source: %s" % item['__src_url']) click.echo() @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) @click.option("--id", multiple=True) @click.option("-n", "--name", multiple=True) @click.option("-a", "--author", multiple=True) @click.option("-k", "--keyword", multiple=True) @click.option("-f", "--framework", multiple=True) @click.option("-p", "--platform", multiple=True) @click.option("-i", "--header", multiple=True) @click.option( "--noninteractive", is_flag=True, help="Do not prompt, automatically paginate with delay") def lib_search(query, json_output, page, noninteractive, **filters): if not query: query = [] if not isinstance(query, list): query = list(query) for key, values in filters.iteritems(): for value in values: query.append('%s:"%s"' % (key, value)) result = get_api_result( "/v2/lib/search", dict(query=" ".join(query), page=page), cache_valid="1d") if json_output: click.echo(json.dumps(result)) return if result['total'] == 0: 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) 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( "https://docs.platformio.org/page/userguide/lib/cmd_search.html\n", fg="cyan") return click.secho( "Found %d libraries:\n" % result['total'], fg="green" if result['total'] else "yellow") while True: for item in result['items']: print_lib_item(item) if (int(result['page']) * int(result['perpage']) >= int( result['total'])): break if noninteractive: click.echo() click.secho( "Loading next %d libraries... Press Ctrl+C to stop!" % result['perpage'], fg="yellow") click.echo() time.sleep(5) elif not click.confirm("Show next libraries?"): break result = get_api_result( "/v2/lib/search", { "query": " ".join(query), "page": int(result['page']) + 1 }, cache_valid="1d") @cli.command("list", short_help="List installed libraries") @click.option("--json-output", is_flag=True) @click.pass_obj def lib_list(lm, json_output): items = lm.get_installed() if json_output: return click.echo(json.dumps(items)) if not items: return None for item in sorted(items, key=lambda i: i['name']): print_lib_item(item) return True @cli.command("builtin", short_help="List built-in libraries") @click.option("--storage", multiple=True) @click.option("--json-output", is_flag=True) def lib_builtin(storage, json_output): items = get_builtin_libs(storage) if json_output: return click.echo(json.dumps(items)) for storage_ in items: if not storage_['items']: continue click.secho(storage_['name'], fg="green") click.echo("*" * len(storage_['name'])) click.echo() for item in sorted(storage_['items'], key=lambda i: i['name']): print_lib_item(item) return True @cli.command("show", short_help="Show detailed info about a library") @click.argument("library", metavar="[LIBRARY]") @click.option("--json-output", is_flag=True) def lib_show(library, json_output): lm = LibraryManager() name, requirements, _ = lm.parse_pkg_uri(library) lib_id = lm.search_lib_id({ "name": name, "requirements": 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)) click.secho(lib['name'], fg="cyan") click.echo("=" * len(lib['name'])) click.secho("#ID: %d" % lib['id'], bold=True) click.echo(lib['description']) click.echo() click.echo( "Version: %s, released %s" % (lib['version']['name'], time.strftime("%c", util.parse_date(lib['version']['released'])))) 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 lib.get("authors", []): _data = [] for key in ("name", "email", "url", "maintainer"): if not author[key]: continue if key == "email": _data.append("<%s>" % author[key]) elif key == "maintainer": _data.append("(maintainer)") else: _data.append(author[key]) _authors.append(" ".join(_data)) if _authors: blocks.append(("Authors", _authors)) blocks.append(("Keywords", lib['keywords'])) for key in ("frameworks", "platforms"): if key not in lib or not lib[key]: continue blocks.append(("Compatible %s" % key, [i['title'] for i in lib[key]])) blocks.append(("Headers", lib['headers'])) blocks.append(("Examples", lib['examples'])) blocks.append(("Versions", [ "%s, released %s" % (v['name'], time.strftime("%c", util.parse_date(v['released']))) 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) return True @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 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") @cli.command("stats", short_help="Library Registry Statistics") @click.option("--json-output", is_flag=True) def lib_stats(json_output): result = get_api_result("/lib/stats", cache_valid="1h") if json_output: return click.echo(json.dumps(result)) printitem_tpl = "{name:<33} {url}" printitemdate_tpl = "{name:<33} {date:23} {url}" def _print_title(title): click.secho(title.upper(), bold=True) click.echo("*" * len(title)) def _print_header(with_date=False): click.echo((printitemdate_tpl if with_date else printitem_tpl).format( name=click.style("Name", fg="cyan"), date="Date", url=click.style("Url", fg="blue"))) terminal_width, _ = click.get_terminal_size() click.echo("-" * terminal_width) def _print_lib_item(item): date = str( time.strftime("%c", util.parse_date(item['date'])) if "date" in item else "") url = click.style( "https://platformio.org/lib/show/%s/%s" % (item['id'], quote(item['name'])), fg="blue") click.echo( (printitemdate_tpl if "date" in item else printitem_tpl).format( name=click.style(item['name'], fg="cyan"), date=date, url=url)) def _print_tag_item(name): click.echo( printitem_tpl.format( name=click.style(name, fg="cyan"), url=click.style( "https://platformio.org/lib/search?query=" + quote( "keyword:%s" % name), fg="blue"))) for key in ("updated", "added"): _print_title("Recently " + key) _print_header(with_date=True) for item in result.get(key, []): _print_lib_item(item) click.echo() _print_title("Recent keywords") _print_header(with_date=False) for item in result.get("lastkeywords"): _print_tag_item(item) click.echo() _print_title("Popular keywords") _print_header(with_date=False) for item in result.get("topkeywords"): _print_tag_item(item) click.echo() for key, title in (("dlday", "Today"), ("dlweek", "Week"), ("dlmonth", "Month")): _print_title("Featured: " + title) _print_header(with_date=False) for item in result.get(key, []): _print_lib_item(item) click.echo() return True