Merge branch 'release/v6.0.2'

This commit is contained in:
Ivan Kravets
2022-06-01 16:27:53 +03:00
180 changed files with 2553 additions and 1629 deletions

View File

@ -14,15 +14,19 @@ jobs:
python-version: "3.6" python-version: "3.6"
- os: windows-latest - os: windows-latest
python-version: "3.10" python-version: "3.10"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: "recursive" submodules: "recursive"
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
@ -31,11 +35,8 @@ jobs:
- name: Python Lint - name: Python Lint
run: | run: |
tox -e lint tox -e lint
- name: Integration Tests - name: Integration Tests
env:
TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}
TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}
TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}
run: | run: |
tox -e testcore tox -e testcore

42
.github/workflows/deployment.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Deployment
on:
push:
branches:
- master
- "release/**"
jobs:
deployment:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
- name: Deployment Tests
env:
TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}
TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}
TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}
run: |
tox -e testcore
- name: Publish package to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@ -2,22 +2,28 @@ name: Examples
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: build:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-18.04, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
python-version: [3.7]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
PIO_INSTALL_DEVPLATFORM_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORM_NAMES: "aceinna_imu,atmelavr,atmelmegaavr,atmelsam,espressif32,espressif8266,nordicnrf52,raspberrypi,ststm32,teensy"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: "recursive" submodules: "recursive"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 - name: Set up Python
uses: actions/setup-python@v3
with: with:
python-version: ${{ matrix.python-version }} python-version: "3.9"
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
@ -25,24 +31,15 @@ jobs:
- name: Run on Linux - name: Run on Linux
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
env:
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,intel_mcs51"
run: | run: |
# ChipKIT issue: install 32-bit support for GCC PIC32
sudo apt-get install libc6-i386
# Free space # Free space
sudo apt clean sudo apt clean
docker rmi $(docker image ls -aq) docker rmi $(docker image ls -aq)
df -h df -h
# Run
tox -e testexamples tox -e testexamples
- name: Run on macOS - name: Run on macOS
if: startsWith(matrix.os, 'macos') if: startsWith(matrix.os, 'macos')
env:
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,microchippic32,lattice_ice40,gd32v"
run: | run: |
df -h df -h
tox -e testexamples tox -e testexamples
@ -52,8 +49,6 @@ jobs:
env: env:
PLATFORMIO_CORE_DIR: C:/pio PLATFORMIO_CORE_DIR: C:/pio
PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,riscv_gap"
run: | run: |
tox -e testexamples tox -e testexamples

69
.github/workflows/projects.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Projects
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
project:
- marlin:
repository: "MarlinFirmware/Marlin"
folder: "Marlin"
config_dir: "Marlin"
env_name: "mega2560"
- esphome:
repository: "esphome/esphome"
folder: "esphome"
config_dir: "esphome"
env_name: "esp32-arduino"
- smartknob:
repository: "scottbez1/smartknob"
folder: "smartknob"
config_dir: "smartknob/firmware"
env_name: "view"
- espurna:
repository: "xoseperez/espurna"
folder: "espurna"
config_dir: "espurna/code"
env_name: "nodemcu-lolin"
- OpenMQTTGateway:
repository: "1technophile/OpenMQTTGateway"
folder: "OpenMQTTGateway"
config_dir: "OpenMQTTGateway"
env_name: "esp32-m5atom"
os: [ubuntu-latest, windows-latest, macos-latest]
exclude:
- os: windows-latest
project: {"esphome": "", "repository": "esphome/esphome", "folder": "esphome", "config_dir": "esphome", "env_name": "esp32-arduino"}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Install PlatformIO
run: pip install -U .
- name: Check out ${{ matrix.project.repository }}
uses: actions/checkout@v2
with:
submodules: "recursive"
repository: ${{ matrix.project.repository }}
path: ${{ matrix.project.folder }}
- name: Install ESPHome dependencies
# Requires esptool package as it's used in a custom prescript
if: ${{ contains(matrix.project.repository, 'esphome') }}
run: pip install esptool==3.*
- name: Compile ${{ matrix.project.repository }}
run: pio run -d ${{ matrix.project.config_dir }} -e ${{ matrix.project.env_name }}

View File

@ -4,6 +4,7 @@ Release Notes
.. |PIOCONF| replace:: `"platformio.ini" <https://docs.platformio.org/en/latest/projectconf.html>`__ configuration file .. |PIOCONF| replace:: `"platformio.ini" <https://docs.platformio.org/en/latest/projectconf.html>`__ configuration file
.. |LDF| replace:: `LDF <https://docs.platformio.org/en/latest/librarymanager/ldf.html>`__ .. |LDF| replace:: `LDF <https://docs.platformio.org/en/latest/librarymanager/ldf.html>`__
.. |INTERPOLATION| replace:: `Interpolation of Values <https://docs.platformio.org/en/latest/projectconf/interpolation.html>`__ .. |INTERPOLATION| replace:: `Interpolation of Values <https://docs.platformio.org/en/latest/projectconf/interpolation.html>`__
.. |UNITTESTING| replace:: `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html>`__
.. _release_notes_6: .. _release_notes_6:
@ -12,6 +13,16 @@ PlatformIO Core 6
**A professional collaborative platform for declarative, safety-critical, and test-driven embedded development.** **A professional collaborative platform for declarative, safety-critical, and test-driven embedded development.**
6.0.2 (2022-06-01)
~~~~~~~~~~~~~~~~~~
* Control |UNITTESTING| verbosity with a new multilevel `pio test -v <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-v>`__ command option (`issue #4276 <https://github.com/platformio/platformio-core/issues/4276>`_)
* Follow symbolic links during searching for the unit test suites (`issue #4288 <https://github.com/platformio/platformio-core/issues/4288>`_)
* Show a warning when testing an empty project without a test suite (`issue #4278 <https://github.com/platformio/platformio-core/issues/4278>`_)
* Improved support for `Asking for input (prompts) <https://docs.platformio.org/en/latest/scripting/examples/asking_for_input.html>`_
* Fixed an issue when the `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__ option was applied outside the project scope (`issue #4277 <https://github.com/platformio/platformio-core/issues/4277>`_)
* Fixed an issue with debugging assembly files without preprocessor (".s")
6.0.1 (2022-05-17) 6.0.1 (2022-05-17)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -53,7 +64,7 @@ Please check the `Migration guide from 5.x to 6.0 <https://docs.platformio.org/e
* **Unit Testing** * **Unit Testing**
- Refactored from scratch `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html>`_ solution and its documentation - Refactored from scratch |UNITTESTING| solution and its documentation
- New: `Test Hierarchy <https://docs.platformio.org/en/latest/advanced/unit-testing/structure.html>`_ (`issue #4135 <https://github.com/platformio/platformio-core/issues/4135>`_) - New: `Test Hierarchy <https://docs.platformio.org/en/latest/advanced/unit-testing/structure.html>`_ (`issue #4135 <https://github.com/platformio/platformio-core/issues/4135>`_)
- New: `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework (`issue #4240 <https://github.com/platformio/platformio-core/issues/4240>`_) - New: `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework (`issue #4240 <https://github.com/platformio/platformio-core/issues/4240>`_)
- New: `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework (`issue #3572 <https://github.com/platformio/platformio-core/issues/3572>`_) - New: `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework (`issue #3572 <https://github.com/platformio/platformio-core/issues/3572>`_)

View File

@ -8,18 +8,18 @@ PlatformIO Core
.. image:: https://github.com/platformio/platformio-core/workflows/Core/badge.svg .. image:: https://github.com/platformio/platformio-core/workflows/Core/badge.svg
:target: https://docs.platformio.org/en/latest/core/index.html :target: https://docs.platformio.org/en/latest/core/index.html
:alt: CI Build for PlatformIO Core :alt: CI Build for PlatformIO Core
.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg
:target: https://github.com/platformio/platformio-examples
:alt: CI Build for dev-platform examples
.. image:: https://github.com/platformio/platformio-core/workflows/Docs/badge.svg .. image:: https://github.com/platformio/platformio-core/workflows/Docs/badge.svg
:target: https://docs.platformio.org?utm_source=github&utm_medium=core :target: https://docs.platformio.org?utm_source=github&utm_medium=core
:alt: CI Build for Docs :alt: CI Build for Docs
.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg
:target: https://github.com/platformio/platformio-examples
:alt: CI Build for dev-platform examples
.. image:: https://github.com/platformio/platformio-core/workflows/Projects/badge.svg
:target: https://docs.platformio.org/en/latest/tutorials/index.html#projects
:alt: CI Build for the Community Projects
.. image:: https://img.shields.io/pypi/v/platformio.svg .. image:: https://img.shields.io/pypi/v/platformio.svg
:target: https://pypi.python.org/pypi/platformio/ :target: https://pypi.python.org/pypi/platformio/
:alt: Latest Version :alt: Latest Version
.. image:: https://img.shields.io/badge/license-Apache%202.0-blue.svg
:target: https://pypi.python.org/pypi/platformio/
:alt: License
.. image:: https://img.shields.io/badge/PlatformIO-Labs-orange.svg .. image:: https://img.shields.io/badge/PlatformIO-Labs-orange.svg
:alt: PlatformIO Labs :alt: PlatformIO Labs
:target: https://piolabs.com/?utm_source=github&utm_medium=core :target: https://piolabs.com/?utm_source=github&utm_medium=core

2
docs

Submodule docs updated: 5bf0037c66...300060ea08

View File

@ -14,7 +14,7 @@
import sys import sys
VERSION = (6, 0, 1) VERSION = (6, 0, 2)
__version__ = ".".join([str(s) for s in VERSION]) __version__ = ".".join([str(s) for s in VERSION])
__title__ = "platformio" __title__ = "platformio"

View File

@ -19,7 +19,7 @@ from traceback import format_exc
import click import click
from platformio import __version__, exception, maintenance from platformio import __version__, exception, maintenance
from platformio.commands import PlatformioCLI from platformio.cli import PlatformioCLI
from platformio.compat import IS_CYGWIN, ensure_python3 from platformio.compat import IS_CYGWIN, ensure_python3
@ -27,7 +27,7 @@ from platformio.compat import IS_CYGWIN, ensure_python3
cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"]) cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"])
) )
@click.version_option(__version__, prog_name="PlatformIO Core") @click.version_option(__version__, prog_name="PlatformIO Core")
@click.option("--force", "-f", is_flag=True, help="DEPRECATED") @click.option("--force", "-f", is_flag=True, help="DEPRECATED", hidden=True)
@click.option("--caller", "-c", help="Caller ID (service)") @click.option("--caller", "-c", help="Caller ID (service)")
@click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters") @click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters")
@click.pass_context @click.pass_context
@ -120,7 +120,7 @@ An unexpected error occurred. Further steps:
`pip install -U platformio` command `pip install -U platformio` command
* Try to find answer in FAQ Troubleshooting section * Try to find answer in FAQ Troubleshooting section
https://docs.platformio.org/page/faq.html https://docs.platformio.org/page/faq/index.html
* Report this problem to the developers * Report this problem to the developers
https://github.com/platformio/platformio-core/issues https://github.com/platformio/platformio-core/issues

44
platformio/account/cli.py Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.commands.destroy import account_destroy_cmd
from platformio.account.commands.forgot import account_forgot_cmd
from platformio.account.commands.login import account_login_cmd
from platformio.account.commands.logout import account_logout_cmd
from platformio.account.commands.password import account_password_cmd
from platformio.account.commands.register import account_register_cmd
from platformio.account.commands.show import account_show_cmd
from platformio.account.commands.token import account_token_cmd
from platformio.account.commands.update import account_update_cmd
@click.group(
"account",
commands=[
account_destroy_cmd,
account_forgot_cmd,
account_login_cmd,
account_logout_cmd,
account_password_cmd,
account_register_cmd,
account_show_cmd,
account_token_cmd,
account_update_cmd,
],
short_help="Manage PlatformIO account",
)
def cli():
pass

View File

@ -16,8 +16,8 @@ import os
import time import time
from platformio import __accounts_api__, app from platformio import __accounts_api__, app
from platformio.clients.http import HTTPClient, HTTPClientError
from platformio.exception import PlatformioException from platformio.exception import PlatformioException
from platformio.http import HTTPClient, HTTPClientError
class AccountError(PlatformioException): class AccountError(PlatformioException):

View File

@ -0,0 +1,37 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient, AccountNotAuthorized
@click.command("destroy", short_help="Destroy account")
def account_destroy_cmd():
client = AccountClient()
click.confirm(
"Are you sure you want to delete the %s user account?\n"
"Warning! All linked data will be permanently removed and can not be restored."
% client.get_logged_username(),
abort=True,
)
client.destroy_account()
try:
client.logout()
except AccountNotAuthorized:
pass
click.secho(
"User account has been destroyed.",
fg="green",
)

View File

@ -12,16 +12,18 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# pylint: disable=unused-argument import click
from platformio.commands.update import cli as cmd_update from platformio.account.client import AccountClient
def test_update(clirunner, validate_cliresult, isolated_pio_core): @click.command("forgot", short_help="Forgot password")
matches = ("Platform Manager", "Library Manager") @click.option("--username", prompt="Username or email")
result = clirunner.invoke(cmd_update) def account_forgot_cmd(username):
validate_cliresult(result) client = AccountClient()
assert all(m in result.output for m in matches) client.forgot_password(username)
result = clirunner.invoke(cmd_update) click.secho(
validate_cliresult(result) "If this account is registered, we will send the "
assert all(m in result.output for m in matches) "further instructions to your email.",
fg="green",
)

View File

@ -0,0 +1,26 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("login", short_help="Log in to PlatformIO Account")
@click.option("-u", "--username", prompt="Username or email")
@click.option("-p", "--password", prompt=True, hide_input=True)
def account_login_cmd(username, password):
client = AccountClient()
client.login(username, password)
click.secho("Successfully logged in!", fg="green")

View File

@ -0,0 +1,24 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("logout", short_help="Log out of PlatformIO Account")
def account_logout_cmd():
client = AccountClient()
client.logout()
click.secho("Successfully logged out!", fg="green")

View File

@ -0,0 +1,26 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("password", short_help="Change password")
@click.option("--old-password", prompt=True, hide_input=True)
@click.option("--new-password", prompt=True, hide_input=True, confirmation_prompt=True)
def account_password_cmd(old_password, new_password):
client = AccountClient()
client.change_password(old_password, new_password)
click.secho("Password successfully changed!", fg="green")

View File

@ -0,0 +1,52 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import (
validate_email,
validate_password,
validate_username,
)
@click.command("register", short_help="Create new PlatformIO Account")
@click.option(
"-u",
"--username",
prompt=True,
callback=lambda _, __, value: validate_username(value),
)
@click.option(
"-e", "--email", prompt=True, callback=lambda _, __, value: validate_email(value)
)
@click.option(
"-p",
"--password",
prompt=True,
hide_input=True,
confirmation_prompt=True,
callback=lambda _, __, value: validate_password(value),
)
@click.option("--firstname", prompt=True)
@click.option("--lastname", prompt=True)
def account_register_cmd(username, email, password, firstname, lastname):
client = AccountClient()
client.registration(username, email, password, firstname, lastname)
click.secho(
"An account has been successfully created. "
"Please check your mail to activate your account and verify your email address.",
fg="green",
)

View File

@ -0,0 +1,116 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 json
import click
from tabulate import tabulate
from platformio import util
from platformio.account.client import AccountClient
@click.command("show", short_help="PlatformIO Account information")
@click.option("--offline", is_flag=True)
@click.option("--json-output", is_flag=True)
def account_show_cmd(offline, json_output):
client = AccountClient()
info = client.get_account_info(offline)
if json_output:
click.echo(json.dumps(info))
return
click.echo()
if info.get("profile"):
print_profile(info["profile"])
if info.get("packages"):
print_packages(info["packages"])
if info.get("subscriptions"):
print_subscriptions(info["subscriptions"])
click.echo()
def print_profile(profile):
click.secho("Profile", fg="cyan", bold=True)
click.echo("=" * len("Profile"))
data = []
if profile.get("username"):
data.append(("Username:", profile["username"]))
if profile.get("email"):
data.append(("Email:", profile["email"]))
if profile.get("firstname"):
data.append(("First name:", profile["firstname"]))
if profile.get("lastname"):
data.append(("Last name:", profile["lastname"]))
click.echo(tabulate(data, tablefmt="plain"))
def print_packages(packages):
click.echo()
click.secho("Packages", fg="cyan")
click.echo("=" * len("Packages"))
for package in packages:
click.echo()
click.secho(package.get("name"), bold=True)
click.echo("-" * len(package.get("name")))
if package.get("description"):
click.echo(package.get("description"))
data = []
expire = "-"
if "subscription" in package:
expire = util.parse_datetime(
package["subscription"].get("end_at")
or package["subscription"].get("next_bill_at")
).strftime("%Y-%m-%d")
data.append(("Expire:", expire))
services = []
for key in package:
if not key.startswith("service."):
continue
if isinstance(package[key], dict):
services.append(package[key].get("title"))
else:
services.append(package[key])
if services:
data.append(("Services:", ", ".join(services)))
click.echo(tabulate(data, tablefmt="plain"))
def print_subscriptions(subscriptions):
click.echo()
click.secho("Subscriptions", fg="cyan")
click.echo("=" * len("Subscriptions"))
for subscription in subscriptions:
click.echo()
click.secho(subscription.get("product_name"), bold=True)
click.echo("-" * len(subscription.get("product_name")))
data = [("State:", subscription.get("status"))]
begin_at = util.parse_datetime(subscription.get("begin_at")).strftime("%c")
data.append(("Start date:", begin_at or "-"))
end_at = subscription.get("end_at")
if end_at:
end_at = util.parse_datetime(subscription.get("end_at")).strftime("%c")
data.append(("End date:", end_at or "-"))
next_bill_at = subscription.get("next_bill_at")
if next_bill_at:
next_bill_at = util.parse_datetime(
subscription.get("next_bill_at")
).strftime("%c")
data.append(("Next payment:", next_bill_at or "-"))
data.append(
("Edit:", click.style(subscription.get("update_url"), fg="blue") or "-")
)
data.append(
("Cancel:", click.style(subscription.get("cancel_url"), fg="blue") or "-")
)
click.echo(tabulate(data, tablefmt="plain"))

View File

@ -0,0 +1,32 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 json
import click
from platformio.account.client import AccountClient
@click.command("token", short_help="Get or regenerate Authentication Token")
@click.option("-p", "--password", prompt=True, hide_input=True)
@click.option("--regenerate", is_flag=True)
@click.option("--json-output", is_flag=True)
def account_token_cmd(password, regenerate, json_output):
client = AccountClient()
auth_token = client.auth_token(password, regenerate)
if json_output:
click.echo(json.dumps({"status": "success", "result": auth_token}))
return
click.secho("Personal Authentication Token: %s" % auth_token, fg="green")

View File

@ -0,0 +1,59 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient, AccountNotAuthorized
from platformio.account.validate import validate_email, validate_username
@click.command("update", short_help="Update profile information")
@click.option("--current-password", prompt=True, hide_input=True)
@click.option("--username")
@click.option("--email")
@click.option("--firstname")
@click.option("--lastname")
def account_update_cmd(current_password, **kwargs):
client = AccountClient()
profile = client.get_profile()
new_profile = profile.copy()
if not any(kwargs.values()):
for field in profile:
new_profile[field] = click.prompt(
field.replace("_", " ").capitalize(), default=profile[field]
)
if field == "email":
validate_email(new_profile[field])
if field == "username":
validate_username(new_profile[field])
else:
new_profile.update({key: value for key, value in kwargs.items() if value})
client.update_profile(new_profile, current_password)
click.secho("Profile successfully updated!", fg="green")
username_changed = new_profile["username"] != profile["username"]
email_changed = new_profile["email"] != profile["email"]
if not username_changed and not email_changed:
return None
try:
client.logout()
except AccountNotAuthorized:
pass
if email_changed:
click.secho(
"Please check your mail to verify your new email address and re-login. ",
fg="yellow",
)
return None
click.secho("Please re-login.", fg="yellow")
return None

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.org.commands.add import org_add_cmd
from platformio.account.org.commands.create import org_create_cmd
from platformio.account.org.commands.destroy import org_destroy_cmd
from platformio.account.org.commands.list import org_list_cmd
from platformio.account.org.commands.remove import org_remove_cmd
from platformio.account.org.commands.update import org_update_cmd
@click.group(
"account",
commands=[
org_add_cmd,
org_create_cmd,
org_destroy_cmd,
org_list_cmd,
org_remove_cmd,
org_update_cmd,
],
short_help="Manage organizations",
)
def cli():
pass

View File

@ -0,0 +1,34 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("add", short_help="Add a new owner to organization")
@click.argument(
"orgname",
)
@click.argument(
"username",
)
def org_add_cmd(orgname, username):
client = AccountClient()
client.add_org_owner(orgname, username)
return click.secho(
"The new owner `%s` has been successfully added to the `%s` organization."
% (username, orgname),
fg="green",
)

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_email, validate_orgname
@click.command("create", short_help="Create a new organization")
@click.argument(
"orgname",
callback=lambda _, __, value: validate_orgname(value),
)
@click.option(
"--email", callback=lambda _, __, value: validate_email(value) if value else value
)
@click.option(
"--displayname",
)
def org_create_cmd(orgname, email, displayname):
client = AccountClient()
client.create_org(orgname, email, displayname)
return click.secho(
"The organization `%s` has been successfully created." % orgname,
fg="green",
)

View File

@ -0,0 +1,34 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("destroy", short_help="Destroy organization")
@click.argument("orgname")
def org_destroy_cmd(orgname):
client = AccountClient()
click.confirm(
"Are you sure you want to delete the `%s` organization account?\n"
"Warning! All linked data will be permanently removed and can not be restored."
% orgname,
abort=True,
)
client.destroy_org(orgname)
return click.secho(
"Organization `%s` has been destroyed." % orgname,
fg="green",
)

View File

@ -0,0 +1,48 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 json
import click
from tabulate import tabulate
from platformio.account.client import AccountClient
@click.command("list", short_help="List organizations and their members")
@click.option("--json-output", is_flag=True)
def org_list_cmd(json_output):
client = AccountClient()
orgs = client.list_orgs()
if json_output:
return click.echo(json.dumps(orgs))
if not orgs:
return click.echo("You do not have any organization")
for org in orgs:
click.echo()
click.secho(org.get("orgname"), fg="cyan")
click.echo("-" * len(org.get("orgname")))
data = []
if org.get("displayname"):
data.append(("Display Name:", org.get("displayname")))
if org.get("email"):
data.append(("Email:", org.get("email")))
data.append(
(
"Owners:",
", ".join((owner.get("username") for owner in org.get("owners"))),
)
)
click.echo(tabulate(data, tablefmt="plain"))
return click.echo()

View File

@ -0,0 +1,34 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
@click.command("remove", short_help="Remove an owner from organization")
@click.argument(
"orgname",
)
@click.argument(
"username",
)
def org_remove_cmd(orgname, username):
client = AccountClient()
client.remove_org_owner(orgname, username)
return click.secho(
"The `%s` owner has been successfully removed from the `%s` organization."
% (username, orgname),
fg="green",
)

View File

@ -0,0 +1,52 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_email, validate_orgname
@click.command("update", short_help="Update organization")
@click.argument("cur_orgname")
@click.option(
"--orgname",
callback=lambda _, __, value: validate_orgname(value),
help="A new orgname",
)
@click.option("--email")
@click.option("--displayname")
def org_update_cmd(cur_orgname, **kwargs):
client = AccountClient()
org = client.get_org(cur_orgname)
del org["owners"]
new_org = org.copy()
if not any(kwargs.values()):
for field in org:
new_org[field] = click.prompt(
field.replace("_", " ").capitalize(), default=org[field]
)
if field == "email":
validate_email(new_org[field])
if field == "orgname":
validate_orgname(new_org[field])
else:
new_org.update(
{key.replace("new_", ""): value for key, value in kwargs.items() if value}
)
client.update_org(cur_orgname, new_org)
return click.secho(
"The organization `%s` has been successfully updated." % cur_orgname,
fg="green",
)

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.team.commands.add import team_add_cmd
from platformio.account.team.commands.create import team_create_cmd
from platformio.account.team.commands.destroy import team_destroy_cmd
from platformio.account.team.commands.list import team_list_cmd
from platformio.account.team.commands.remove import team_remove_cmd
from platformio.account.team.commands.update import team_update_cmd
@click.group(
"team",
commands=[
team_add_cmd,
team_create_cmd,
team_destroy_cmd,
team_list_cmd,
team_remove_cmd,
team_update_cmd,
],
short_help="Manage organization teams",
)
def cli():
pass

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_orgname_teamname
@click.command("add", short_help="Add a new member to team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.argument(
"username",
)
def team_add_cmd(orgname_teamname, username):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.add_team_member(orgname, teamname, username)
return click.secho(
"The new member %s has been successfully added to the %s team."
% (username, teamname),
fg="green",
)

View File

@ -0,0 +1,39 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_orgname_teamname
@click.command("create", short_help="Create a new team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(
value, teamname_validate=True
),
)
@click.option(
"--description",
)
def team_create_cmd(orgname_teamname, description):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.create_team(orgname, teamname, description)
return click.secho(
"The team %s has been successfully created." % teamname,
fg="green",
)

View File

@ -0,0 +1,40 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_orgname_teamname
@click.command("destroy", short_help="Destroy a team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
def team_destroy_cmd(orgname_teamname):
orgname, teamname = orgname_teamname.split(":", 1)
click.confirm(
click.style(
"Are you sure you want to destroy the %s team?" % teamname, fg="yellow"
),
abort=True,
)
client = AccountClient()
client.destroy_team(orgname, teamname)
return click.secho(
"The team %s has been successfully destroyed." % teamname,
fg="green",
)

View File

@ -0,0 +1,59 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 json
import click
from tabulate import tabulate
from platformio.account.client import AccountClient
@click.command("list", short_help="List teams")
@click.argument("orgname", required=False)
@click.option("--json-output", is_flag=True)
def team_list_cmd(orgname, json_output):
client = AccountClient()
data = {}
if not orgname:
for item in client.list_orgs():
teams = client.list_teams(item.get("orgname"))
data[item.get("orgname")] = teams
else:
teams = client.list_teams(orgname)
data[orgname] = teams
if json_output:
return click.echo(json.dumps(data[orgname] if orgname else data))
if not any(data.values()):
return click.secho("You do not have any teams.", fg="yellow")
for org_name in data:
for team in data[org_name]:
click.echo()
click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan")
click.echo("-" * len("%s:%s" % (org_name, team.get("name"))))
table_data = []
if team.get("description"):
table_data.append(("Description:", team.get("description")))
table_data.append(
(
"Members:",
", ".join(
(member.get("username") for member in team.get("members"))
)
if team.get("members")
else "-",
)
)
click.echo(tabulate(table_data, tablefmt="plain"))
return click.echo()

View File

@ -0,0 +1,36 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_orgname_teamname
@click.command("remove", short_help="Remove a member from team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.argument("username")
def team_remove_cmd(orgname_teamname, username):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.remove_team_member(orgname, teamname, username)
return click.secho(
"The %s member has been successfully removed from the %s team."
% (username, teamname),
fg="green",
)

View File

@ -0,0 +1,55 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 click
from platformio.account.client import AccountClient
from platformio.account.validate import validate_orgname_teamname, validate_teamname
@click.command("update", short_help="Update team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.option(
"--name",
callback=lambda _, __, value: validate_teamname(value),
help="A new team name",
)
@click.option(
"--description",
)
def team_update_cmd(orgname_teamname, **kwargs):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
team = client.get_team(orgname, teamname)
del team["id"]
del team["members"]
new_team = team.copy()
if not any(kwargs.values()):
for field in team:
new_team[field] = click.prompt(
field.replace("_", " ").capitalize(), default=team[field]
)
if field == "name":
validate_teamname(new_team[field])
else:
new_team.update({key: value for key, value in kwargs.items() if value})
client.update_team(orgname, teamname, new_team)
return click.secho(
"The team %s has been successfully updated." % teamname,
fg="green",
)

View File

@ -0,0 +1,79 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 re
import click
def validate_username(value, field="username"):
value = str(value).strip()
if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I):
raise click.BadParameter(
"Invalid %s format. "
"%s must contain only alphanumeric characters "
"or single hyphens, cannot begin or end with a hyphen, "
"and must not be longer than 38 characters."
% (field.lower(), field.capitalize())
)
return value
def validate_email(value):
value = str(value).strip()
if not re.match(r"^[a-z\d_.+-]+@[a-z\d\-]+\.[a-z\d\-.]+$", value, flags=re.I):
raise click.BadParameter("Invalid email address")
return value
def validate_password(value):
value = str(value).strip()
if not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value):
raise click.BadParameter(
"Invalid password format. "
"Password must contain at least 8 characters"
" including a number and a lowercase letter"
)
return value
def validate_orgname(value):
return validate_username(value, "Organization name")
def validate_orgname_teamname(value, teamname_validate=False):
if ":" not in value:
raise click.BadParameter(
"Please specify organization and team name in the next"
" format - orgname:teamname. For example, mycompany:DreamTeam"
)
teamname = str(value.strip().split(":", 1)[1])
if teamname_validate:
validate_teamname(teamname)
return value
def validate_teamname(value):
if not value:
return value
value = str(value).strip()
if not re.match(r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I):
raise click.BadParameter(
"Invalid team name format. "
"Team name must only contain alphanumeric characters, "
"single hyphens, underscores, spaces. It can not "
"begin or end with a hyphen or a underscore and must"
" not be longer than 20 characters."
)
return value

View File

@ -225,7 +225,7 @@ if "envdump" in COMMAND_LINE_TARGETS:
click.echo(env.Dump()) click.echo(env.Dump())
env.Exit(0) env.Exit(0)
if set(["_idedata", "idedata"]) & set(COMMAND_LINE_TARGETS): if env.IsIntegrationDump():
projenv = None projenv = None
try: try:
Import("projenv") Import("projenv")

View File

@ -225,11 +225,15 @@ class InoToCPPConverter(object):
return "\n".join(result) return "\n".join(result)
def ConvertInoToCpp(env): def FindInoNodes(env):
src_dir = glob.escape(env.subst("$PROJECT_SRC_DIR")) src_dir = glob.escape(env.subst("$PROJECT_SRC_DIR"))
ino_nodes = env.Glob(os.path.join(src_dir, "*.ino")) + env.Glob( return env.Glob(os.path.join(src_dir, "*.ino")) + env.Glob(
os.path.join(src_dir, "*.pde") os.path.join(src_dir, "*.pde")
) )
def ConvertInoToCpp(env):
ino_nodes = env.FindInoNodes()
if not ino_nodes: if not ino_nodes:
return return
c = InoToCPPConverter(env) c = InoToCPPConverter(env)
@ -247,6 +251,7 @@ def _delete_file(path):
def generate(env): def generate(env):
env.AddMethod(FindInoNodes)
env.AddMethod(ConvertInoToCpp) env.AddMethod(ConvertInoToCpp)

View File

@ -19,10 +19,15 @@ import os
import SCons.Defaults # pylint: disable=import-error import SCons.Defaults # pylint: disable=import-error
import SCons.Subst # pylint: disable=import-error import SCons.Subst # pylint: disable=import-error
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
from platformio.proc import exec_command, where_is_program from platformio.proc import exec_command, where_is_program
def IsIntegrationDump(_):
return set(["_idedata", "idedata"]) & set(COMMAND_LINE_TARGETS)
def DumpIntegrationIncludes(env): def DumpIntegrationIncludes(env):
result = dict(build=[], compatlib=[], toolchain=[]) result = dict(build=[], compatlib=[], toolchain=[])
@ -60,7 +65,7 @@ def DumpIntegrationIncludes(env):
return result return result
def _get_gcc_defines(env): def get_gcc_defines(env):
items = [] items = []
try: try:
sysenv = os.environ.copy() sysenv = os.environ.copy()
@ -83,7 +88,7 @@ def _get_gcc_defines(env):
return items return items
def _dump_defines(env): def dump_defines(env):
defines = [] defines = []
# global symbols # global symbols
for item in SCons.Defaults.processDefines(env.get("CPPDEFINES", [])): for item in SCons.Defaults.processDefines(env.get("CPPDEFINES", [])):
@ -108,12 +113,12 @@ def _dump_defines(env):
# built-in GCC marcos # built-in GCC marcos
# if env.GetCompilerType() == "gcc": # if env.GetCompilerType() == "gcc":
# defines.extend(_get_gcc_defines(env)) # defines.extend(get_gcc_defines(env))
return defines return defines
def _get_svd_path(env): def dump_svd_path(env):
svd_path = env.GetProjectOption("debug_svd_path") svd_path = env.GetProjectOption("debug_svd_path")
if svd_path: if svd_path:
return os.path.abspath(svd_path) return os.path.abspath(svd_path)
@ -146,13 +151,13 @@ def DumpIntegrationData(env, globalenv):
data = { data = {
"env_name": env["PIOENV"], "env_name": env["PIOENV"],
"libsource_dirs": [env.subst(item) for item in env.GetLibSourceDirs()], "libsource_dirs": [env.subst(item) for item in env.GetLibSourceDirs()],
"defines": _dump_defines(env), "defines": dump_defines(env),
"includes": env.DumpIntegrationIncludes(), "includes": env.DumpIntegrationIncludes(),
"cc_path": where_is_program(env.subst("$CC"), env.subst("${ENV['PATH']}")), "cc_path": where_is_program(env.subst("$CC"), env.subst("${ENV['PATH']}")),
"cxx_path": where_is_program(env.subst("$CXX"), env.subst("${ENV['PATH']}")), "cxx_path": where_is_program(env.subst("$CXX"), env.subst("${ENV['PATH']}")),
"gdb_path": where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")), "gdb_path": where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")),
"prog_path": env.subst("$PROG_PATH"), "prog_path": env.subst("$PROG_PATH"),
"svd_path": _get_svd_path(env), "svd_path": dump_svd_path(env),
"compiler_type": env.GetCompilerType(), "compiler_type": env.GetCompilerType(),
"targets": globalenv.DumpTargets(), "targets": globalenv.DumpTargets(),
"extra": dict( "extra": dict(
@ -162,7 +167,9 @@ def DumpIntegrationData(env, globalenv):
] ]
), ),
} }
data["extra"].update(env.get("IDE_EXTRA_DATA", {})) data["extra"].update(
env.get("INTEGRATION_EXTRA_DATA", env.get("IDE_EXTRA_DATA", {}))
)
env_ = env.Clone() env_ = env.Clone()
# https://github.com/platformio/platformio-atom-ide/issues/34 # https://github.com/platformio/platformio-atom-ide/issues/34
@ -191,6 +198,7 @@ def exists(_):
def generate(env): def generate(env):
env.AddMethod(IsIntegrationDump)
env.AddMethod(DumpIntegrationIncludes) env.AddMethod(DumpIntegrationIncludes)
env.AddMethod(DumpIntegrationData) env.AddMethod(DumpIntegrationData)
return env return env

View File

@ -31,8 +31,8 @@ from SCons.Script import DefaultEnvironment # pylint: disable=import-error
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.builder.tools import platformio as piotool from platformio.builder.tools import platformio as piotool
from platformio.clients.http import HTTPClientError, InternetIsOffline
from platformio.compat import IS_WINDOWS, hashlib_encode_data, string_types from platformio.compat import IS_WINDOWS, hashlib_encode_data, string_types
from platformio.http import HTTPClientError, InternetIsOffline
from platformio.package.exception import ( from platformio.package.exception import (
MissingPackageManifestError, MissingPackageManifestError,
UnknownPackageError, UnknownPackageError,
@ -60,16 +60,16 @@ class LibBuilderFactory(object):
elif used_frameworks: elif used_frameworks:
clsname = "%sLibBuilder" % used_frameworks[0].capitalize() clsname = "%sLibBuilder" % used_frameworks[0].capitalize()
obj = getattr(sys.modules[__name__], clsname)(env, path, verbose=verbose) obj = globals()[clsname](env, path, verbose=verbose)
# Handle PlatformIOLibBuilder.manifest.build.builder # Handle PlatformIOLibBuilder.manifest.build.builder
# pylint: disable=protected-access # pylint: disable=protected-access
if isinstance(obj, PlatformIOLibBuilder) and obj._manifest.get("build", {}).get( if isinstance(obj, PlatformIOLibBuilder) and obj._manifest.get("build", {}).get(
"builder" "builder"
): ):
obj = getattr( obj = globals()[obj._manifest.get("build", {}).get("builder")](
sys.modules[__name__], obj._manifest.get("build", {}).get("builder") env, path, verbose=verbose
)(env, path, verbose=verbose) )
assert isinstance(obj, LibBuilderBase) assert isinstance(obj, LibBuilderBase)
return obj return obj
@ -111,7 +111,7 @@ class LibBuilderFactory(object):
return [] return []
class LibBuilderBase(object): class LibBuilderBase:
CLASSIC_SCANNER = SCons.Scanner.C.CScanner() CLASSIC_SCANNER = SCons.Scanner.C.CScanner()
CCONDITIONAL_SCANNER = SCons.Scanner.C.CConditionalScanner() CCONDITIONAL_SCANNER = SCons.Scanner.C.CConditionalScanner()
@ -453,11 +453,17 @@ class LibBuilderBase(object):
def build(self): def build(self):
libs = [] libs = []
shared_scopes = ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS")
for lb in self.depbuilders: for lb in self.depbuilders:
libs.extend(lb.build()) libs.extend(lb.build())
# copy shared information to self env # copy shared information to self env
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): self.env.PrependUnique(
self.env.PrependUnique(**{key: lb.env.get(key)}) **{
scope: lb.env.get(scope)
for scope in shared_scopes
if lb.env.get(scope)
}
)
for lb in self._circular_deps: for lb in self._circular_deps:
self.env.PrependUnique(CPPPATH=lb.get_include_dirs()) self.env.PrependUnique(CPPPATH=lb.get_include_dirs())
@ -472,8 +478,13 @@ class LibBuilderBase(object):
for lb in self.env.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if self == lb or not lb.is_built: if self == lb or not lb.is_built:
continue continue
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): self.env.PrependUnique(
self.env.PrependUnique(**{key: lb.env.get(key)}) **{
scope: lb.env.get(scope)
for scope in shared_scopes
if lb.env.get(scope)
}
)
do_not_archive = not self.lib_archive do_not_archive = not self.lib_archive
if not do_not_archive: if not do_not_archive:
@ -514,7 +525,7 @@ class ArduinoLibBuilder(LibBuilderBase):
return os.path.join(self.path, "include") return os.path.join(self.path, "include")
def get_include_dirs(self): def get_include_dirs(self):
include_dirs = LibBuilderBase.get_include_dirs(self) include_dirs = super().get_include_dirs()
if os.path.isdir(os.path.join(self.path, "src")): if os.path.isdir(os.path.join(self.path, "src")):
return include_dirs return include_dirs
if os.path.isdir(os.path.join(self.path, "utility")): if os.path.isdir(os.path.join(self.path, "utility")):
@ -612,7 +623,7 @@ class MbedLibBuilder(LibBuilderBase):
return LibBuilderBase.src_dir.fget(self) # pylint: disable=no-member return LibBuilderBase.src_dir.fget(self) # pylint: disable=no-member
def get_include_dirs(self): def get_include_dirs(self):
include_dirs = LibBuilderBase.get_include_dirs(self) include_dirs = super().get_include_dirs()
if self.path not in include_dirs: if self.path not in include_dirs:
include_dirs.append(self.path) include_dirs.append(self.path)
@ -833,7 +844,7 @@ class PlatformIOLibBuilder(LibBuilderBase):
return util.items_in_list(frameworks, self._manifest.get("frameworks") or ["*"]) return util.items_in_list(frameworks, self._manifest.get("frameworks") or ["*"])
def get_include_dirs(self): def get_include_dirs(self):
include_dirs = LibBuilderBase.get_include_dirs(self) include_dirs = super().get_include_dirs()
# backwards compatibility with PlatformIO 2.0 # backwards compatibility with PlatformIO 2.0
if ( if (
@ -872,14 +883,14 @@ class ProjectAsLibBuilder(LibBuilderBase):
project_include_dir = self.env.subst("$PROJECT_INCLUDE_DIR") project_include_dir = self.env.subst("$PROJECT_INCLUDE_DIR")
if os.path.isdir(project_include_dir): if os.path.isdir(project_include_dir):
include_dirs.append(project_include_dir) include_dirs.append(project_include_dir)
for include_dir in LibBuilderBase.get_include_dirs(self): for include_dir in super().get_include_dirs():
if include_dir not in include_dirs: if include_dir not in include_dirs:
include_dirs.append(include_dir) include_dirs.append(include_dir)
return include_dirs return include_dirs
def get_search_files(self): def get_search_files(self):
# project files # project files
items = LibBuilderBase.get_search_files(self) items = super().get_search_files()
# test files # test files
if "test" in self.env.GetBuildType(): if "test" in self.env.GetBuildType():
items.extend( items.extend(
@ -994,7 +1005,7 @@ class ProjectAsLibBuilder(LibBuilderBase):
def build(self): def build(self):
self.is_built = True # do not build Project now self.is_built = True # do not build Project now
result = LibBuilderBase.build(self) result = super().build()
self.env.PrependUnique(CPPPATH=self.get_include_dirs()) self.env.PrependUnique(CPPPATH=self.get_include_dirs())
return result return result
@ -1027,14 +1038,15 @@ def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)))
return True return True
def GetLibBuilders(env): # pylint: disable=too-many-branches def GetLibBuilders(_): # pylint: disable=too-many-branches
if DefaultEnvironment().get("__PIO_LIB_BUILDERS", None) is not None: env = DefaultEnvironment()
if env.get("__PIO_LIB_BUILDERS", None) is not None:
return sorted( return sorted(
DefaultEnvironment()["__PIO_LIB_BUILDERS"], env["__PIO_LIB_BUILDERS"],
key=lambda lb: 0 if lb.is_dependent else 1, key=lambda lb: 0 if lb.is_dependent else 1,
) )
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=[]) env.Replace(__PIO_LIB_BUILDERS=[])
verbose = int(ARGUMENTS.get("PIOVERBOSE", 0)) verbose = int(ARGUMENTS.get("PIOVERBOSE", 0))
found_incompat = False found_incompat = False
@ -1060,13 +1072,13 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
) )
continue continue
if env.IsCompatibleLibBuilder(lb): if env.IsCompatibleLibBuilder(lb):
DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb]) env.Append(__PIO_LIB_BUILDERS=[lb])
else: else:
found_incompat = True found_incompat = True
for lb in env.get("EXTRA_LIB_BUILDERS", []): for lb in env.get("EXTRA_LIB_BUILDERS", []):
if env.IsCompatibleLibBuilder(lb): if env.IsCompatibleLibBuilder(lb):
DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb]) env.Append(__PIO_LIB_BUILDERS=[lb])
else: else:
found_incompat = True found_incompat = True
@ -1077,7 +1089,7 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
"ldf-compat-mode\n" "ldf-compat-mode\n"
) )
return DefaultEnvironment()["__PIO_LIB_BUILDERS"] return env["__PIO_LIB_BUILDERS"]
def ConfigureProjectLibBuilder(env): def ConfigureProjectLibBuilder(env):

View File

@ -114,7 +114,15 @@ def ConfigureDebugTarget(env):
] ]
if optimization_flags: if optimization_flags:
env.AppendUnique(ASFLAGS=optimization_flags, LINKFLAGS=optimization_flags) env.AppendUnique(
ASFLAGS=[
# skip -O flags for assembler
f
for f in optimization_flags
if f.startswith("-g")
],
LINKFLAGS=optimization_flags,
)
def GetExtraScripts(env, scope): def GetExtraScripts(env, scope):

View File

@ -143,6 +143,7 @@ def ProcessProgramDeps(env):
def ProcessProjectDeps(env): def ProcessProjectDeps(env):
project_lib_builder = env.ConfigureProjectLibBuilder() project_lib_builder = env.ConfigureProjectLibBuilder()
projenv = project_lib_builder.env
# prepend project libs to the beginning of list # prepend project libs to the beginning of list
env.Prepend(LIBS=project_lib_builder.build()) env.Prepend(LIBS=project_lib_builder.build())
@ -155,17 +156,18 @@ def ProcessProjectDeps(env):
} }
) )
projenv = env.Clone()
# CPPPATH from dependencies
projenv.PrependUnique(CPPPATH=project_lib_builder.env.get("CPPPATH"))
# extra build flags from `platformio.ini`
projenv.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
if "test" in env.GetBuildType(): if "test" in env.GetBuildType():
build_files_before_nums = len(env.get("PIOBUILDFILES", []))
projenv.BuildSources( projenv.BuildSources(
"$BUILD_TEST_DIR", "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER" "$BUILD_TEST_DIR", "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER"
) )
if len(env.get("PIOBUILDFILES", [])) - build_files_before_nums < 1:
sys.stderr.write(
"Error: Nothing to build. Please put your test suites "
"to the '%s' folder\n" % env.subst("$PROJECT_TEST_DIR")
)
env.Exit(1)
if "test" not in env.GetBuildType() or env.GetProjectOption("test_build_src"): if "test" not in env.GetBuildType() or env.GetProjectOption("test_build_src"):
projenv.BuildSources( projenv.BuildSources(
"$BUILD_SRC_DIR", "$PROJECT_SRC_DIR", env.get("SRC_FILTER") "$BUILD_SRC_DIR", "$PROJECT_SRC_DIR", env.get("SRC_FILTER")
@ -174,7 +176,7 @@ def ProcessProjectDeps(env):
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS: if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
sys.stderr.write( sys.stderr.write(
"Error: Nothing to build. Please put your source code files " "Error: Nothing to build. Please put your source code files "
"to '%s' folder\n" % env.subst("$PROJECT_SRC_DIR") "to the '%s' folder\n" % env.subst("$PROJECT_SRC_DIR")
) )
env.Exit(1) env.Exit(1)
@ -327,25 +329,18 @@ def BuildFrameworks(env, frameworks):
) )
env.Exit(1) env.Exit(1)
board_frameworks = env.BoardConfig().get("frameworks", []) supported_frameworks = env.BoardConfig().get("frameworks", [])
if frameworks == ["platformio"]: for name in frameworks:
if board_frameworks: if name == "arduino":
frameworks.insert(0, board_frameworks[0]) # Arduino IDE appends .o to the end of filename
else:
sys.stderr.write("Error: Please specify `board` in `platformio.ini`\n")
env.Exit(1)
for f in frameworks:
if f == "arduino":
# Arduino IDE appends .o the end of filename
Builder.match_splitext = scons_patched_match_splitext Builder.match_splitext = scons_patched_match_splitext
if "nobuild" not in COMMAND_LINE_TARGETS: if "nobuild" not in COMMAND_LINE_TARGETS:
env.ConvertInoToCpp() env.ConvertInoToCpp()
if f in board_frameworks: if name in supported_frameworks:
SConscript(env.GetFrameworkScript(f), exports="env") SConscript(env.GetFrameworkScript(name), exports="env")
else: else:
sys.stderr.write("Error: This board doesn't support %s framework!\n" % f) sys.stderr.write("Error: This board doesn't support %s framework!\n" % name)
env.Exit(1) env.Exit(1)

View File

@ -26,8 +26,8 @@ import click
from tabulate import tabulate from tabulate import tabulate
from platformio import app, exception, fs, util from platformio import app, exception, fs, util
from platformio.commands.check.defect import DefectItem from platformio.check.defect import DefectItem
from platformio.commands.check.tools import CheckToolFactory from platformio.check.tools import CheckToolFactory
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import find_project_dir_above, get_project_dir from platformio.project.helpers import find_project_dir_above, get_project_dir

View File

@ -13,9 +13,9 @@
# limitations under the License. # limitations under the License.
from platformio import exception from platformio import exception
from platformio.commands.check.tools.clangtidy import ClangtidyCheckTool from platformio.check.tools.clangtidy import ClangtidyCheckTool
from platformio.commands.check.tools.cppcheck import CppcheckCheckTool from platformio.check.tools.cppcheck import CppcheckCheckTool
from platformio.commands.check.tools.pvsstudio import PvsStudioCheckTool from platformio.check.tools.pvsstudio import PvsStudioCheckTool
class CheckToolFactory(object): class CheckToolFactory(object):

View File

@ -19,7 +19,7 @@ import tempfile
import click import click
from platformio import fs, proc from platformio import fs, proc
from platformio.commands.check.defect import DefectItem from platformio.check.defect import DefectItem
from platformio.package.manager.core import get_core_package_dir from platformio.package.manager.core import get_core_package_dir
from platformio.package.meta import PackageSpec from platformio.package.meta import PackageSpec
from platformio.project.helpers import load_build_metadata from platformio.project.helpers import load_build_metadata

View File

@ -15,8 +15,8 @@
import re import re
from os.path import join from os.path import join
from platformio.commands.check.defect import DefectItem from platformio.check.defect import DefectItem
from platformio.commands.check.tools.base import CheckToolBase from platformio.check.tools.base import CheckToolBase
class ClangtidyCheckTool(CheckToolBase): class ClangtidyCheckTool(CheckToolBase):

View File

@ -17,8 +17,8 @@ import os
import click import click
from platformio import proc from platformio import proc
from platformio.commands.check.defect import DefectItem from platformio.check.defect import DefectItem
from platformio.commands.check.tools.base import CheckToolBase from platformio.check.tools.base import CheckToolBase
class CppcheckCheckTool(CheckToolBase): class CppcheckCheckTool(CheckToolBase):

View File

@ -20,8 +20,8 @@ from xml.etree.ElementTree import fromstring
import click import click
from platformio import proc from platformio import proc
from platformio.commands.check.defect import DefectItem from platformio.check.defect import DefectItem
from platformio.commands.check.tools.base import CheckToolBase from platformio.check.tools.base import CheckToolBase
from platformio.compat import IS_WINDOWS from platformio.compat import IS_WINDOWS

96
platformio/cli.py Normal file
View File

@ -0,0 +1,96 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 importlib
from pathlib import Path
import click
class PlatformioCLI(click.MultiCommand):
leftover_args = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._pio_root_path = Path(__file__).parent
self._pio_cmd_aliases = dict(package="pkg")
def _find_pio_commands(self):
def _to_module_path(p):
return (
"platformio." + ".".join(p.relative_to(self._pio_root_path).parts)[:-3]
)
result = {}
for p in self._pio_root_path.rglob("cli.py"):
# skip this module
if p.parent == self._pio_root_path:
continue
cmd_name = p.parent.name
result[self._pio_cmd_aliases.get(cmd_name, cmd_name)] = _to_module_path(p)
# find legacy commands
for p in (self._pio_root_path / "commands").iterdir():
if p.name.startswith("_"):
continue
if (p / "command.py").is_file():
result[p.name] = _to_module_path(p / "command.py")
elif p.name.endswith(".py"):
result[p.name[:-3]] = _to_module_path(p)
return result
@staticmethod
def in_silence():
args = PlatformioCLI.leftover_args
return args and any(
[
args[0] == "debug" and "--interpreter" in " ".join(args),
args[0] == "upgrade",
"--json-output" in args,
"--version" in args,
]
)
def invoke(self, ctx):
PlatformioCLI.leftover_args = ctx.args
if hasattr(ctx, "protected_args"):
PlatformioCLI.leftover_args = ctx.protected_args + ctx.args
return super().invoke(ctx)
def list_commands(self, ctx):
return sorted(list(self._find_pio_commands()))
def get_command(self, ctx, cmd_name):
commands = self._find_pio_commands()
if cmd_name not in commands:
return self._handle_obsolate_command(ctx, cmd_name)
module = importlib.import_module(commands[cmd_name])
return getattr(module, "cli")
@staticmethod
def _handle_obsolate_command(ctx, cmd_name):
# pylint: disable=import-outside-toplevel
if cmd_name == "init":
from platformio.project.commands.init import project_init_cmd
return project_init_cmd
if cmd_name == "package":
from platformio.package.cli import cli
return cli
raise click.UsageError('No such command "%s"' % cmd_name, ctx)

View File

@ -11,76 +11,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
import click
class PlatformioCLI(click.MultiCommand):
leftover_args = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._pio_cmds_dir = os.path.dirname(__file__)
@staticmethod
def in_silence():
args = PlatformioCLI.leftover_args
return args and any(
[
args[0] == "debug" and "--interpreter" in " ".join(args),
args[0] == "upgrade",
"--json-output" in args,
"--version" in args,
]
)
def invoke(self, ctx):
PlatformioCLI.leftover_args = ctx.args
if hasattr(ctx, "protected_args"):
PlatformioCLI.leftover_args = ctx.protected_args + ctx.args
return super().invoke(ctx)
def list_commands(self, ctx):
cmds = []
for cmd_name in os.listdir(self._pio_cmds_dir):
if cmd_name.startswith("__init__"):
continue
if os.path.isfile(os.path.join(self._pio_cmds_dir, cmd_name, "command.py")):
cmds.append(cmd_name)
elif cmd_name.endswith(".py"):
cmds.append(cmd_name[:-3])
cmds.sort()
return cmds
def get_command(self, ctx, cmd_name):
mod = None
try:
mod_path = "platformio.commands." + cmd_name
if os.path.isfile(os.path.join(self._pio_cmds_dir, cmd_name, "command.py")):
mod_path = "platformio.commands.%s.command" % cmd_name
mod = __import__(mod_path, None, None, ["cli"])
except ImportError:
try:
return self._handle_obsolate_command(cmd_name)
except AttributeError:
pass
raise click.UsageError('No such command "%s"' % cmd_name, ctx)
return mod.cli
@staticmethod
def _handle_obsolate_command(name):
# pylint: disable=import-outside-toplevel
if name == "init":
from platformio.project.commands.init import project_init_cmd
return project_init_cmd
if name == "package":
from platformio.commands.pkg import cli
return cli
raise AttributeError()

View File

@ -1,154 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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=unused-argument
import json
import re
import click
from tabulate import tabulate
from platformio.clients.registry import RegistryClient
from platformio.commands.account import validate_username
from platformio.commands.team import validate_orgname_teamname
def validate_client(value):
if ":" in value:
validate_orgname_teamname(value)
else:
validate_username(value)
return value
@click.group("access", short_help="Manage resource access")
def cli():
pass
def validate_urn(value):
value = str(value).strip()
if not re.match(r"^prn:reg:pkg:(\d+):(\w+)$", value, flags=re.I):
raise click.BadParameter("Invalid URN format.")
return value
@cli.command("public", short_help="Make resource public")
@click.argument(
"urn",
callback=lambda _, __, value: validate_urn(value),
)
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
def access_public(urn, urn_type):
client = RegistryClient()
client.update_resource(urn=urn, private=0)
return click.secho(
"The resource %s has been successfully updated." % urn,
fg="green",
)
@cli.command("private", short_help="Make resource private")
@click.argument(
"urn",
callback=lambda _, __, value: validate_urn(value),
)
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
def access_private(urn, urn_type):
client = RegistryClient()
client.update_resource(urn=urn, private=1)
return click.secho(
"The resource %s has been successfully updated." % urn,
fg="green",
)
@cli.command("grant", short_help="Grant access")
@click.argument("level", type=click.Choice(["admin", "maintainer", "guest"]))
@click.argument(
"client",
metavar="[<ORGNAME:TEAMNAME>|<USERNAME>]",
callback=lambda _, __, value: validate_client(value),
)
@click.argument(
"urn",
callback=lambda _, __, value: validate_urn(value),
)
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
def access_grant(level, client, urn, urn_type):
reg_client = RegistryClient()
reg_client.grant_access_for_resource(urn=urn, client=client, level=level)
return click.secho(
"Access for resource %s has been granted for %s" % (urn, client),
fg="green",
)
@cli.command("revoke", short_help="Revoke access")
@click.argument(
"client",
metavar="[ORGNAME:TEAMNAME|USERNAME]",
callback=lambda _, __, value: validate_client(value),
)
@click.argument(
"urn",
callback=lambda _, __, value: validate_urn(value),
)
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
def access_revoke(client, urn, urn_type):
reg_client = RegistryClient()
reg_client.revoke_access_from_resource(urn=urn, client=client)
return click.secho(
"Access for resource %s has been revoked for %s" % (urn, client),
fg="green",
)
@cli.command("list", short_help="List published resources")
@click.argument("owner", required=False)
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
@click.option("--json-output", is_flag=True)
def access_list(owner, urn_type, json_output):
reg_client = RegistryClient()
resources = reg_client.list_resources(owner=owner)
if json_output:
return click.echo(json.dumps(resources))
if not resources:
return click.secho("You do not have any resources.", fg="yellow")
for resource in resources:
click.echo()
click.secho(resource.get("name"), fg="cyan")
click.echo("-" * len(resource.get("name")))
table_data = []
table_data.append(("URN:", resource.get("urn")))
table_data.append(("Owner:", resource.get("owner")))
table_data.append(
(
"Access:",
click.style("Private", fg="red")
if resource.get("private", False)
else "Public",
)
)
table_data.append(
(
"Access level(s):",
", ".join(
(level.capitalize() for level in resource.get("access_levels"))
),
)
)
click.echo(tabulate(table_data, tablefmt="plain"))
return click.echo()

View File

@ -1,292 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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=unused-argument
import json
import re
import click
from tabulate import tabulate
from platformio import util
from platformio.clients.account import AccountClient, AccountNotAuthorized
@click.group("account", short_help="Manage PlatformIO account")
def cli():
pass
def validate_username(value, field="username"):
value = str(value).strip()
if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I):
raise click.BadParameter(
"Invalid %s format. "
"%s must contain only alphanumeric characters "
"or single hyphens, cannot begin or end with a hyphen, "
"and must not be longer than 38 characters."
% (field.lower(), field.capitalize())
)
return value
def validate_email(value):
value = str(value).strip()
if not re.match(r"^[a-z\d_.+-]+@[a-z\d\-]+\.[a-z\d\-.]+$", value, flags=re.I):
raise click.BadParameter("Invalid email address")
return value
def validate_password(value):
value = str(value).strip()
if not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value):
raise click.BadParameter(
"Invalid password format. "
"Password must contain at least 8 characters"
" including a number and a lowercase letter"
)
return value
@cli.command("register", short_help="Create new PlatformIO Account")
@click.option(
"-u",
"--username",
prompt=True,
callback=lambda _, __, value: validate_username(value),
)
@click.option(
"-e", "--email", prompt=True, callback=lambda _, __, value: validate_email(value)
)
@click.option(
"-p",
"--password",
prompt=True,
hide_input=True,
confirmation_prompt=True,
callback=lambda _, __, value: validate_password(value),
)
@click.option("--firstname", prompt=True)
@click.option("--lastname", prompt=True)
def account_register(username, email, password, firstname, lastname):
client = AccountClient()
client.registration(username, email, password, firstname, lastname)
return click.secho(
"An account has been successfully created. "
"Please check your mail to activate your account and verify your email address.",
fg="green",
)
@cli.command("login", short_help="Log in to PlatformIO Account")
@click.option("-u", "--username", prompt="Username or email")
@click.option("-p", "--password", prompt=True, hide_input=True)
def account_login(username, password):
client = AccountClient()
client.login(username, password)
return click.secho("Successfully logged in!", fg="green")
@cli.command("logout", short_help="Log out of PlatformIO Account")
def account_logout():
client = AccountClient()
client.logout()
return click.secho("Successfully logged out!", fg="green")
@cli.command("password", short_help="Change password")
@click.option("--old-password", prompt=True, hide_input=True)
@click.option("--new-password", prompt=True, hide_input=True, confirmation_prompt=True)
def account_password(old_password, new_password):
client = AccountClient()
client.change_password(old_password, new_password)
return click.secho("Password successfully changed!", fg="green")
@cli.command("token", short_help="Get or regenerate Authentication Token")
@click.option("-p", "--password", prompt=True, hide_input=True)
@click.option("--regenerate", is_flag=True)
@click.option("--json-output", is_flag=True)
def account_token(password, regenerate, json_output):
client = AccountClient()
auth_token = client.auth_token(password, regenerate)
if json_output:
return click.echo(json.dumps({"status": "success", "result": auth_token}))
return click.secho("Personal Authentication Token: %s" % auth_token, fg="green")
@cli.command("forgot", short_help="Forgot password")
@click.option("--username", prompt="Username or email")
def account_forgot(username):
client = AccountClient()
client.forgot_password(username)
return click.secho(
"If this account is registered, we will send the "
"further instructions to your email.",
fg="green",
)
@cli.command("update", short_help="Update profile information")
@click.option("--current-password", prompt=True, hide_input=True)
@click.option("--username")
@click.option("--email")
@click.option("--firstname")
@click.option("--lastname")
def account_update(current_password, **kwargs):
client = AccountClient()
profile = client.get_profile()
new_profile = profile.copy()
if not any(kwargs.values()):
for field in profile:
new_profile[field] = click.prompt(
field.replace("_", " ").capitalize(), default=profile[field]
)
if field == "email":
validate_email(new_profile[field])
if field == "username":
validate_username(new_profile[field])
else:
new_profile.update({key: value for key, value in kwargs.items() if value})
client.update_profile(new_profile, current_password)
click.secho("Profile successfully updated!", fg="green")
username_changed = new_profile["username"] != profile["username"]
email_changed = new_profile["email"] != profile["email"]
if not username_changed and not email_changed:
return None
try:
client.logout()
except AccountNotAuthorized:
pass
if email_changed:
return click.secho(
"Please check your mail to verify your new email address and re-login. ",
fg="yellow",
)
return click.secho("Please re-login.", fg="yellow")
@cli.command("destroy", short_help="Destroy account")
def account_destroy():
client = AccountClient()
click.confirm(
"Are you sure you want to delete the %s user account?\n"
"Warning! All linked data will be permanently removed and can not be restored."
% client.get_logged_username(),
abort=True,
)
client.destroy_account()
try:
client.logout()
except AccountNotAuthorized:
pass
return click.secho(
"User account has been destroyed.",
fg="green",
)
@cli.command("show", short_help="PlatformIO Account information")
@click.option("--offline", is_flag=True)
@click.option("--json-output", is_flag=True)
def account_show(offline, json_output):
client = AccountClient()
info = client.get_account_info(offline)
if json_output:
return click.echo(json.dumps(info))
click.echo()
if info.get("profile"):
print_profile(info["profile"])
if info.get("packages"):
print_packages(info["packages"])
if info.get("subscriptions"):
print_subscriptions(info["subscriptions"])
return click.echo()
def print_profile(profile):
click.secho("Profile", fg="cyan", bold=True)
click.echo("=" * len("Profile"))
data = []
if profile.get("username"):
data.append(("Username:", profile["username"]))
if profile.get("email"):
data.append(("Email:", profile["email"]))
if profile.get("firstname"):
data.append(("First name:", profile["firstname"]))
if profile.get("lastname"):
data.append(("Last name:", profile["lastname"]))
click.echo(tabulate(data, tablefmt="plain"))
def print_packages(packages):
click.echo()
click.secho("Packages", fg="cyan")
click.echo("=" * len("Packages"))
for package in packages:
click.echo()
click.secho(package.get("name"), bold=True)
click.echo("-" * len(package.get("name")))
if package.get("description"):
click.echo(package.get("description"))
data = []
expire = "-"
if "subscription" in package:
expire = util.parse_datetime(
package["subscription"].get("end_at")
or package["subscription"].get("next_bill_at")
).strftime("%Y-%m-%d")
data.append(("Expire:", expire))
services = []
for key in package:
if not key.startswith("service."):
continue
if isinstance(package[key], dict):
services.append(package[key].get("title"))
else:
services.append(package[key])
if services:
data.append(("Services:", ", ".join(services)))
click.echo(tabulate(data, tablefmt="plain"))
def print_subscriptions(subscriptions):
click.echo()
click.secho("Subscriptions", fg="cyan")
click.echo("=" * len("Subscriptions"))
for subscription in subscriptions:
click.echo()
click.secho(subscription.get("product_name"), bold=True)
click.echo("-" * len(subscription.get("product_name")))
data = [("State:", subscription.get("status"))]
begin_at = util.parse_datetime(subscription.get("begin_at")).strftime("%c")
data.append(("Start date:", begin_at or "-"))
end_at = subscription.get("end_at")
if end_at:
end_at = util.parse_datetime(subscription.get("end_at")).strftime("%c")
data.append(("End date:", end_at or "-"))
next_bill_at = subscription.get("next_bill_at")
if next_bill_at:
next_bill_at = util.parse_datetime(
subscription.get("next_bill_at")
).strftime("%c")
data.append(("Next payment:", next_bill_at or "-"))
data.append(
("Edit:", click.style(subscription.get("update_url"), fg="blue") or "-")
)
data.append(
("Cancel:", click.style(subscription.get("cancel_url"), fg="blue") or "-")
)
click.echo(tabulate(data, tablefmt="plain"))

View File

@ -20,10 +20,10 @@ import tempfile
import click import click
from platformio import app, fs from platformio import app, fs
from platformio.commands.run.command import cli as cmd_run
from platformio.exception import CIBuildEnvsEmpty from platformio.exception import CIBuildEnvsEmpty
from platformio.project.commands.init import project_init_cmd, validate_boards from platformio.project.commands.init import project_init_cmd, validate_boards
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.run.cli import cli as cmd_run
def validate_path(ctx, param, value): # pylint: disable=unused-argument def validate_path(ctx, param, value): # pylint: disable=unused-argument

View File

@ -24,7 +24,7 @@ import click
from tabulate import tabulate from tabulate import tabulate
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.commands import PlatformioCLI from platformio.cli import PlatformioCLI
from platformio.commands.lib.helpers import get_builtin_libs, save_project_libdeps from platformio.commands.lib.helpers import get_builtin_libs, save_project_libdeps
from platformio.package.exception import NotGlobalLibDir, UnknownPackageError from platformio.package.exception import NotGlobalLibDir, UnknownPackageError
from platformio.package.manager.library import LibraryPackageManager from platformio.package.manager.library import LibraryPackageManager
@ -69,13 +69,6 @@ def get_project_global_lib_dir():
@click.pass_context @click.pass_context
def cli(ctx, **options): def cli(ctx, **options):
in_silence = PlatformioCLI.in_silence() in_silence = PlatformioCLI.in_silence()
if not in_silence:
click.secho(
"\nWARNING!!! This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg` instead.\n",
fg="yellow",
)
storage_cmds = ("install", "uninstall", "update", "list") storage_cmds = ("install", "uninstall", "update", "list")
# skip commands that don't need storage folder # skip commands that don't need storage folder
if ctx.invoked_subcommand not in storage_cmds or ( if ctx.invoked_subcommand not in storage_cmds or (
@ -148,6 +141,11 @@ def cli(ctx, **options):
def lib_install( # pylint: disable=too-many-arguments,unused-argument def lib_install( # pylint: disable=too-many-arguments,unused-argument
ctx, libraries, save, silent, interactive, force ctx, libraries, save, silent, interactive, force
): ):
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg install` instead.\n",
fg="yellow",
)
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, []) storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, [])
@ -211,6 +209,11 @@ def _save_deps(ctx, pkgs, action="add"):
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting") @click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.pass_context @click.pass_context
def lib_uninstall(ctx, libraries, save, silent): def lib_uninstall(ctx, libraries, save, silent):
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
fg="yellow",
)
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
uninstalled_pkgs = {} uninstalled_pkgs = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
@ -246,6 +249,13 @@ def lib_update( # pylint: disable=too-many-arguments
"This command is deprecated, please use `pio pkg outdated` instead" "This command is deprecated, please use `pio pkg outdated` instead"
) )
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg update` instead.\n",
fg="yellow",
)
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
json_result = {} json_result = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
@ -305,6 +315,12 @@ def lib_update( # pylint: disable=too-many-arguments
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
@click.pass_context @click.pass_context
def lib_list(ctx, json_output): def lib_list(ctx, json_output):
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg list` instead.\n",
fg="yellow",
)
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
json_result = {} json_result = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
@ -348,6 +364,12 @@ def lib_list(ctx, json_output):
help="Do not prompt, automatically paginate with delay", help="Do not prompt, automatically paginate with delay",
) )
def lib_search(query, json_output, page, noninteractive, **filters): def lib_search(query, json_output, page, noninteractive, **filters):
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg search` instead.\n",
fg="yellow",
)
regclient = LibraryPackageManager().get_registry_client_instance() regclient = LibraryPackageManager().get_registry_client_instance()
if not query: if not query:
query = [] query = []
@ -444,6 +466,12 @@ def lib_builtin(storage, json_output):
@click.argument("library", metavar="[LIBRARY]") @click.argument("library", metavar="[LIBRARY]")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def lib_show(library, json_output): def lib_show(library, json_output):
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg show` instead.\n",
fg="yellow",
)
lm = LibraryPackageManager() lm = LibraryPackageManager()
lm.set_log_level(logging.ERROR if json_output else logging.DEBUG) lm.set_log_level(logging.ERROR if json_output else logging.DEBUG)
lib_id = lm.reveal_registry_package_id(library) lib_id = lm.reveal_registry_package_id(library)

View File

@ -1,165 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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=unused-argument
import json
import click
from tabulate import tabulate
from platformio.clients.account import AccountClient
from platformio.commands.account import validate_email, validate_username
@click.group("org", short_help="Manage organizations")
def cli():
pass
def validate_orgname(value):
return validate_username(value, "Organization name")
@cli.command("create", short_help="Create a new organization")
@click.argument(
"orgname",
callback=lambda _, __, value: validate_orgname(value),
)
@click.option(
"--email", callback=lambda _, __, value: validate_email(value) if value else value
)
@click.option(
"--displayname",
)
def org_create(orgname, email, displayname):
client = AccountClient()
client.create_org(orgname, email, displayname)
return click.secho(
"The organization `%s` has been successfully created." % orgname,
fg="green",
)
@cli.command("list", short_help="List organizations and their members")
@click.option("--json-output", is_flag=True)
def org_list(json_output):
client = AccountClient()
orgs = client.list_orgs()
if json_output:
return click.echo(json.dumps(orgs))
if not orgs:
return click.echo("You do not have any organization")
for org in orgs:
click.echo()
click.secho(org.get("orgname"), fg="cyan")
click.echo("-" * len(org.get("orgname")))
data = []
if org.get("displayname"):
data.append(("Display Name:", org.get("displayname")))
if org.get("email"):
data.append(("Email:", org.get("email")))
data.append(
(
"Owners:",
", ".join((owner.get("username") for owner in org.get("owners"))),
)
)
click.echo(tabulate(data, tablefmt="plain"))
return click.echo()
@cli.command("update", short_help="Update organization")
@click.argument("cur_orgname")
@click.option(
"--orgname",
callback=lambda _, __, value: validate_orgname(value),
help="A new orgname",
)
@click.option("--email")
@click.option("--displayname")
def org_update(cur_orgname, **kwargs):
client = AccountClient()
org = client.get_org(cur_orgname)
del org["owners"]
new_org = org.copy()
if not any(kwargs.values()):
for field in org:
new_org[field] = click.prompt(
field.replace("_", " ").capitalize(), default=org[field]
)
if field == "email":
validate_email(new_org[field])
if field == "orgname":
validate_orgname(new_org[field])
else:
new_org.update(
{key.replace("new_", ""): value for key, value in kwargs.items() if value}
)
client.update_org(cur_orgname, new_org)
return click.secho(
"The organization `%s` has been successfully updated." % cur_orgname,
fg="green",
)
@cli.command("destroy", short_help="Destroy organization")
@click.argument("orgname")
def account_destroy(orgname):
client = AccountClient()
click.confirm(
"Are you sure you want to delete the `%s` organization account?\n"
"Warning! All linked data will be permanently removed and can not be restored."
% orgname,
abort=True,
)
client.destroy_org(orgname)
return click.secho(
"Organization `%s` has been destroyed." % orgname,
fg="green",
)
@cli.command("add", short_help="Add a new owner to organization")
@click.argument(
"orgname",
)
@click.argument(
"username",
)
def org_add_owner(orgname, username):
client = AccountClient()
client.add_org_owner(orgname, username)
return click.secho(
"The new owner `%s` has been successfully added to the `%s` organization."
% (username, orgname),
fg="green",
)
@cli.command("remove", short_help="Remove an owner from organization")
@click.argument(
"orgname",
)
@click.argument(
"username",
)
def org_remove_owner(orgname, username):
client = AccountClient()
client.remove_org_owner(orgname, username)
return click.secho(
"The `%s` owner has been successfully removed from the `%s` organization."
% (username, orgname),
fg="green",
)

View File

@ -18,7 +18,6 @@ import os
import click import click
from platformio.commands import PlatformioCLI
from platformio.commands.boards import print_boards from platformio.commands.boards import print_boards
from platformio.exception import UserSideException from platformio.exception import UserSideException
from platformio.package.exception import UnknownPackageError from platformio.package.exception import UnknownPackageError
@ -31,18 +30,19 @@ from platformio.platform.factory import PlatformFactory
@click.group(short_help="Platform manager", hidden=True) @click.group(short_help="Platform manager", hidden=True)
def cli(): def cli():
if not PlatformioCLI.in_silence(): pass
click.secho(
"\nWARNING!!! This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg` instead.\n",
fg="yellow",
)
@cli.command("search", short_help="Search for development platform") @cli.command("search", short_help="Search for development platform")
@click.argument("query", required=False) @click.argument("query", required=False)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_search(query, json_output): def platform_search(query, json_output):
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg search` instead.\n",
fg="yellow",
)
platforms = [] platforms = []
for platform in _get_registry_platforms(): for platform in _get_registry_platforms():
if query == "all": if query == "all":
@ -94,6 +94,12 @@ def platform_frameworks(query, json_output):
@cli.command("list", short_help="List installed development platforms") @cli.command("list", short_help="List installed development platforms")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_list(json_output): def platform_list(json_output):
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg list` instead.\n",
fg="yellow",
)
platforms = [] platforms = []
pm = PlatformPackageManager() pm = PlatformPackageManager()
for pkg in pm.get_installed(): for pkg in pm.get_installed():
@ -112,6 +118,12 @@ def platform_list(json_output):
@click.argument("platform") @click.argument("platform")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_show(platform, json_output): # pylint: disable=too-many-branches def platform_show(platform, json_output): # pylint: disable=too-many-branches
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg show` instead.\n",
fg="yellow",
)
data = _get_platform_data(platform) data = _get_platform_data(platform)
if not data: if not data:
raise UnknownPlatform(platform) raise UnknownPlatform(platform)
@ -195,6 +207,12 @@ def platform_install( # pylint: disable=too-many-arguments,too-many-locals
silent, silent,
force, force,
): ):
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg install` instead.\n",
fg="yellow",
)
def _find_pkg_names(p, candidates): def _find_pkg_names(p, candidates):
result = [] result = []
for candidate in candidates: for candidate in candidates:
@ -251,6 +269,11 @@ def platform_install( # pylint: disable=too-many-arguments,too-many-locals
@cli.command("uninstall", short_help="Uninstall development platform") @cli.command("uninstall", short_help="Uninstall development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]") @click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
def platform_uninstall(platforms): def platform_uninstall(platforms):
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
fg="yellow",
)
pm = PlatformPackageManager() pm = PlatformPackageManager()
pm.set_log_level(logging.DEBUG) pm.set_log_level(logging.DEBUG)
for platform in platforms: for platform in platforms:
@ -285,6 +308,13 @@ def platform_update( # pylint: disable=too-many-locals, too-many-arguments
"This command is deprecated, please use `pio pkg outdated` instead" "This command is deprecated, please use `pio pkg outdated` instead"
) )
if not json_output:
click.secho(
"\nWARNING: This command is deprecated and will be removed in "
"the next releases. \nPlease use `pio pkg update` instead.\n",
fg="yellow",
)
pm = PlatformPackageManager() pm = PlatformPackageManager()
pm.set_log_level(logging.WARN if silent else logging.DEBUG) pm.set_log_level(logging.WARN if silent else logging.DEBUG)
platforms = platforms or pm.get_installed() platforms = platforms or pm.get_installed()

View File

@ -1,191 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 json
import platform
import sys
import click
from tabulate import tabulate
from platformio import __version__, compat, fs, proc, util
from platformio.commands.system.completion import (
ShellType,
get_completion_install_path,
install_completion_code,
uninstall_completion_code,
)
from platformio.commands.system.prune import (
prune_cached_data,
prune_core_packages,
prune_platform_packages,
)
from platformio.package.manager.library import LibraryPackageManager
from platformio.package.manager.platform import PlatformPackageManager
from platformio.package.manager.tool import ToolPackageManager
from platformio.project.config import ProjectConfig
@click.group("system", short_help="Miscellaneous system commands")
def cli():
pass
@cli.command("info", short_help="Display system-wide information")
@click.option("--json-output", is_flag=True)
def system_info(json_output):
project_config = ProjectConfig()
data = {}
data["core_version"] = {"title": "PlatformIO Core", "value": __version__}
data["python_version"] = {
"title": "Python",
"value": "{0}.{1}.{2}-{3}.{4}".format(*list(sys.version_info)),
}
data["system"] = {"title": "System Type", "value": util.get_systype()}
data["platform"] = {"title": "Platform", "value": platform.platform(terse=True)}
data["filesystem_encoding"] = {
"title": "File System Encoding",
"value": compat.get_filesystem_encoding(),
}
data["locale_encoding"] = {
"title": "Locale Encoding",
"value": compat.get_locale_encoding(),
}
data["core_dir"] = {
"title": "PlatformIO Core Directory",
"value": project_config.get("platformio", "core_dir"),
}
data["platformio_exe"] = {
"title": "PlatformIO Core Executable",
"value": proc.where_is_program(
"platformio.exe" if compat.IS_WINDOWS else "platformio"
),
}
data["python_exe"] = {
"title": "Python Executable",
"value": proc.get_pythonexe_path(),
}
data["global_lib_nums"] = {
"title": "Global Libraries",
"value": len(LibraryPackageManager().get_installed()),
}
data["dev_platform_nums"] = {
"title": "Development Platforms",
"value": len(PlatformPackageManager().get_installed()),
}
data["package_tool_nums"] = {
"title": "Tools & Toolchains",
"value": len(
ToolPackageManager(
project_config.get("platformio", "packages_dir")
).get_installed()
),
}
click.echo(
json.dumps(data)
if json_output
else tabulate([(item["title"], item["value"]) for item in data.values()])
)
@cli.command("prune", short_help="Remove unused data")
@click.option("--force", "-f", is_flag=True, help="Do not prompt for confirmation")
@click.option(
"--dry-run", is_flag=True, help="Do not prune, only show data that will be removed"
)
@click.option("--cache", is_flag=True, help="Prune only cached data")
@click.option(
"--core-packages", is_flag=True, help="Prune only unnecessary core packages"
)
@click.option(
"--platform-packages",
is_flag=True,
help="Prune only unnecessary development platform packages",
)
def system_prune(force, dry_run, cache, core_packages, platform_packages):
if dry_run:
click.secho(
"Dry run mode (do not prune, only show data that will be removed)",
fg="yellow",
)
click.echo()
reclaimed_cache = 0
reclaimed_core_packages = 0
reclaimed_platform_packages = 0
prune_all = not any([cache, core_packages, platform_packages])
if cache or prune_all:
reclaimed_cache = prune_cached_data(force, dry_run)
click.echo()
if core_packages or prune_all:
reclaimed_core_packages = prune_core_packages(force, dry_run)
click.echo()
if platform_packages or prune_all:
reclaimed_platform_packages = prune_platform_packages(force, dry_run)
click.echo()
click.secho(
"Total reclaimed space: %s"
% fs.humanize_file_size(
reclaimed_cache + reclaimed_core_packages + reclaimed_platform_packages
),
fg="green",
)
@cli.group("completion", short_help="Shell completion support")
def completion():
pass
@completion.command("install", short_help="Install shell completion files/code")
@click.argument("shell", type=click.Choice([t.value for t in ShellType]))
@click.option(
"--path",
type=click.Path(file_okay=True, dir_okay=False, readable=True, resolve_path=True),
help="Custom installation path of the code to be evaluated by the shell. "
"The standard installation path is used by default.",
)
def completion_install(shell, path):
shell = ShellType(shell)
path = path or get_completion_install_path(shell)
install_completion_code(shell, path)
click.echo(
"PlatformIO CLI completion has been installed for %s shell to %s \n"
"Please restart a current shell session."
% (click.style(shell.name, fg="cyan"), click.style(path, fg="blue"))
)
@completion.command("uninstall", short_help="Uninstall shell completion files/code")
@click.argument("shell", type=click.Choice([t.value for t in ShellType]))
@click.option(
"--path",
type=click.Path(file_okay=True, dir_okay=False, readable=True, resolve_path=True),
help="Custom installation path of the code to be evaluated by the shell. "
"The standard installation path is used by default.",
)
def completion_uninstall(shell, path):
shell = ShellType(shell)
path = path or get_completion_install_path(shell)
uninstall_completion_code(shell, path)
click.echo(
"PlatformIO CLI completion has been uninstalled for %s shell from %s \n"
"Please restart a current shell session."
% (click.style(shell.name, fg="cyan"), click.style(path, fg="blue"))
)

View File

@ -1,212 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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=unused-argument
import json
import re
import click
from tabulate import tabulate
from platformio.clients.account import AccountClient
def validate_orgname_teamname(value, teamname_validate=False):
if ":" not in value:
raise click.BadParameter(
"Please specify organization and team name in the next"
" format - orgname:teamname. For example, mycompany:DreamTeam"
)
teamname = str(value.strip().split(":", 1)[1])
if teamname_validate:
validate_teamname(teamname)
return value
def validate_teamname(value):
if not value:
return value
value = str(value).strip()
if not re.match(r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I):
raise click.BadParameter(
"Invalid team name format. "
"Team name must only contain alphanumeric characters, "
"single hyphens, underscores, spaces. It can not "
"begin or end with a hyphen or a underscore and must"
" not be longer than 20 characters."
)
return value
@click.group("team", short_help="Manage organization teams")
def cli():
pass
@cli.command("create", short_help="Create a new team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(
value, teamname_validate=True
),
)
@click.option(
"--description",
)
def team_create(orgname_teamname, description):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.create_team(orgname, teamname, description)
return click.secho(
"The team %s has been successfully created." % teamname,
fg="green",
)
@cli.command("list", short_help="List teams")
@click.argument("orgname", required=False)
@click.option("--json-output", is_flag=True)
def team_list(orgname, json_output):
client = AccountClient()
data = {}
if not orgname:
for item in client.list_orgs():
teams = client.list_teams(item.get("orgname"))
data[item.get("orgname")] = teams
else:
teams = client.list_teams(orgname)
data[orgname] = teams
if json_output:
return click.echo(json.dumps(data[orgname] if orgname else data))
if not any(data.values()):
return click.secho("You do not have any teams.", fg="yellow")
for org_name in data:
for team in data[org_name]:
click.echo()
click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan")
click.echo("-" * len("%s:%s" % (org_name, team.get("name"))))
table_data = []
if team.get("description"):
table_data.append(("Description:", team.get("description")))
table_data.append(
(
"Members:",
", ".join(
(member.get("username") for member in team.get("members"))
)
if team.get("members")
else "-",
)
)
click.echo(tabulate(table_data, tablefmt="plain"))
return click.echo()
@cli.command("update", short_help="Update team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.option(
"--name",
callback=lambda _, __, value: validate_teamname(value),
help="A new team name",
)
@click.option(
"--description",
)
def team_update(orgname_teamname, **kwargs):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
team = client.get_team(orgname, teamname)
del team["id"]
del team["members"]
new_team = team.copy()
if not any(kwargs.values()):
for field in team:
new_team[field] = click.prompt(
field.replace("_", " ").capitalize(), default=team[field]
)
if field == "name":
validate_teamname(new_team[field])
else:
new_team.update({key: value for key, value in kwargs.items() if value})
client.update_team(orgname, teamname, new_team)
return click.secho(
"The team %s has been successfully updated." % teamname,
fg="green",
)
@cli.command("destroy", short_help="Destroy a team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
def team_destroy(orgname_teamname):
orgname, teamname = orgname_teamname.split(":", 1)
click.confirm(
click.style(
"Are you sure you want to destroy the %s team?" % teamname, fg="yellow"
),
abort=True,
)
client = AccountClient()
client.destroy_team(orgname, teamname)
return click.secho(
"The team %s has been successfully destroyed." % teamname,
fg="green",
)
@cli.command("add", short_help="Add a new member to team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.argument(
"username",
)
def team_add_member(orgname_teamname, username):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.add_team_member(orgname, teamname, username)
return click.secho(
"The new member %s has been successfully added to the %s team."
% (username, teamname),
fg="green",
)
@cli.command("remove", short_help="Remove a member from team")
@click.argument(
"orgname_teamname",
metavar="ORGNAME:TEAMNAME",
callback=lambda _, __, value: validate_orgname_teamname(value),
)
@click.argument("username")
def team_remove_owner(orgname_teamname, username):
orgname, teamname = orgname_teamname.split(":", 1)
client = AccountClient()
client.remove_team_member(orgname, teamname, username)
return click.secho(
"The %s member has been successfully removed from the %s team."
% (username, teamname),
fg="green",
)

View File

@ -20,8 +20,8 @@ from zipfile import ZipFile
import click import click
from platformio import VERSION, __version__, app, exception from platformio import VERSION, __version__, app, exception
from platformio.clients.http import fetch_remote_content
from platformio.compat import IS_WINDOWS from platformio.compat import IS_WINDOWS
from platformio.http import fetch_remote_content
from platformio.package.manager.core import update_core_packages from platformio.package.manager.core import update_core_packages
from platformio.proc import exec_command, get_pythonexe_path from platformio.proc import exec_command, get_pythonexe_path
from platformio.project.helpers import get_project_cache_dir from platformio.project.helpers import get_project_cache_dir

View File

@ -61,7 +61,7 @@ from platformio.project.options import ProjectOptions
@click.option("--interface", type=click.Choice(["gdb"])) @click.option("--interface", type=click.Choice(["gdb"]))
@click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED) @click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED)
@click.pass_context @click.pass_context
def debug_cmd( def cli(
ctx, ctx,
project_dir, project_dir,
project_conf, project_conf,

View File

@ -20,12 +20,12 @@ from fnmatch import fnmatch
from hashlib import sha1 from hashlib import sha1
from io import BytesIO from io import BytesIO
from platformio.commands import PlatformioCLI from platformio.cli import PlatformioCLI
from platformio.commands.run.command import cli as cmd_run
from platformio.commands.run.command import print_processing_header
from platformio.compat import IS_WINDOWS, is_bytes from platformio.compat import IS_WINDOWS, is_bytes
from platformio.debug.exception import DebugInvalidOptionsError from platformio.debug.exception import DebugInvalidOptionsError
from platformio.device.list import list_serial_ports from platformio.device.list import list_serial_ports
from platformio.run.cli import cli as cmd_run
from platformio.run.cli import print_processing_header
from platformio.test.helpers import list_test_names from platformio.test.helpers import list_test_names
from platformio.test.result import TestSuite from platformio.test.result import TestSuite
from platformio.test.runners.base import TestRunnerOptions from platformio.test.runners.base import TestRunnerOptions

View File

@ -56,7 +56,7 @@ class MissedUdevRules(InvalidUdevRules):
MESSAGE = ( MESSAGE = (
"Warning! Please install `99-platformio-udev.rules`. \nMore details: " "Warning! Please install `99-platformio-udev.rules`. \nMore details: "
"https://docs.platformio.org/page/faq.html#platformio-udev-rules" "https://docs.platformio.org/en/latest/core/installation/udev-rules.html"
) )
@ -65,7 +65,7 @@ class OutdatedUdevRules(InvalidUdevRules):
MESSAGE = ( MESSAGE = (
"Warning! Your `{0}` are outdated. Please update or reinstall them." "Warning! Your `{0}` are outdated. Please update or reinstall them."
"\nMore details: " "\nMore details: "
"https://docs.platformio.org/page/faq.html#platformio-udev-rules" "https://docs.platformio.org/en/latest/core/installation/udev-rules.html"
) )

View File

@ -16,8 +16,8 @@ import mimetypes
import click import click
from platformio.commands.home.helpers import is_port_used from platformio.home.helpers import is_port_used
from platformio.commands.home.run import run_server from platformio.home.run import run_server
@click.command("home", short_help="GUI to manage PlatformIO") @click.command("home", short_help="GUI to manage PlatformIO")

View File

@ -14,7 +14,7 @@
from ajsonrpc.core import JSONRPC20DispatchException from ajsonrpc.core import JSONRPC20DispatchException
from platformio.clients.account import AccountClient from platformio.account.client import AccountClient
class AccountRPC: class AccountRPC:

View File

@ -16,8 +16,8 @@ import json
import time import time
from platformio.cache import ContentCache from platformio.cache import ContentCache
from platformio.commands.home.rpc.handlers.os import OSRPC
from platformio.compat import aio_create_task from platformio.compat import aio_create_task
from platformio.home.rpc.handlers.os import OSRPC
class MiscRPC: class MiscRPC:

View File

@ -24,9 +24,9 @@ import click
from platformio import __default_requests_timeout__, fs from platformio import __default_requests_timeout__, fs
from platformio.cache import ContentCache from platformio.cache import ContentCache
from platformio.clients.http import ensure_internet_on
from platformio.commands.home import helpers
from platformio.device.list import list_logical_devices from platformio.device.list import list_logical_devices
from platformio.home import helpers
from platformio.http import ensure_internet_on
class OSRPC: class OSRPC:

View File

@ -25,8 +25,8 @@ from ajsonrpc.core import JSONRPC20DispatchException
from starlette.concurrency import run_in_threadpool from starlette.concurrency import run_in_threadpool
from platformio import __main__, __version__, fs, proc from platformio import __main__, __version__, fs, proc
from platformio.commands.home import helpers
from platformio.compat import get_locale_encoding, is_bytes from platformio.compat import get_locale_encoding, is_bytes
from platformio.home import helpers
class MultiThreadingStdStream(object): class MultiThreadingStdStream(object):

View File

@ -21,8 +21,8 @@ import time
from ajsonrpc.core import JSONRPC20DispatchException from ajsonrpc.core import JSONRPC20DispatchException
from platformio import exception, fs from platformio import exception, fs
from platformio.commands.home.rpc.handlers.app import AppRPC from platformio.home.rpc.handlers.app import AppRPC
from platformio.commands.home.rpc.handlers.piocore import PIOCoreRPC from platformio.home.rpc.handlers.piocore import PIOCoreRPC
from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manager.platform import PlatformPackageManager
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.exception import ProjectError from platformio.project.exception import ProjectError

View File

@ -24,16 +24,16 @@ from starlette.routing import Mount, Route, WebSocketRoute
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from starlette.status import HTTP_403_FORBIDDEN from starlette.status import HTTP_403_FORBIDDEN
from platformio.commands.home.rpc.handlers.account import AccountRPC
from platformio.commands.home.rpc.handlers.app import AppRPC
from platformio.commands.home.rpc.handlers.ide import IDERPC
from platformio.commands.home.rpc.handlers.misc import MiscRPC
from platformio.commands.home.rpc.handlers.os import OSRPC
from platformio.commands.home.rpc.handlers.piocore import PIOCoreRPC
from platformio.commands.home.rpc.handlers.project import ProjectRPC
from platformio.commands.home.rpc.server import WebSocketJSONRPCServerFactory
from platformio.compat import aio_get_running_loop from platformio.compat import aio_get_running_loop
from platformio.exception import PlatformioException from platformio.exception import PlatformioException
from platformio.home.rpc.handlers.account import AccountRPC
from platformio.home.rpc.handlers.app import AppRPC
from platformio.home.rpc.handlers.ide import IDERPC
from platformio.home.rpc.handlers.misc import MiscRPC
from platformio.home.rpc.handlers.os import OSRPC
from platformio.home.rpc.handlers.piocore import PIOCoreRPC
from platformio.home.rpc.handlers.project import ProjectRPC
from platformio.home.rpc.server import WebSocketJSONRPCServerFactory
from platformio.package.manager.core import get_core_package_dir from platformio.package.manager.core import get_core_package_dir
from platformio.proc import force_exit from platformio.proc import force_exit

View File

@ -115,7 +115,7 @@ class HTTPClient(object):
) )
if with_authorization and "Authorization" not in headers: if with_authorization and "Authorization" not in headers:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from platformio.clients.account import AccountClient from platformio.account.client import AccountClient
headers["Authorization"] = ( headers["Authorization"] = (
"Bearer %s" % AccountClient().fetch_authentication_token() "Bearer %s" % AccountClient().fetch_authentication_token()

View File

@ -21,15 +21,15 @@ import semantic_version
from platformio import __version__, app, exception, fs, telemetry from platformio import __version__, app, exception, fs, telemetry
from platformio.cache import cleanup_content_cache from platformio.cache import cleanup_content_cache
from platformio.clients import http from platformio.cli import PlatformioCLI
from platformio.commands import PlatformioCLI
from platformio.commands.platform import platform_update as cmd_platform_update from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.commands.system.prune import calculate_unnecessary_system_data
from platformio.commands.upgrade import get_latest_version from platformio.commands.upgrade import get_latest_version
from platformio.http import HTTPClientError, InternetIsOffline, ensure_internet_on
from platformio.package.manager.core import update_core_packages from platformio.package.manager.core import update_core_packages
from platformio.package.manager.tool import ToolPackageManager from platformio.package.manager.tool import ToolPackageManager
from platformio.package.meta import PackageSpec from platformio.package.meta import PackageSpec
from platformio.package.version import pepver_to_semver from platformio.package.version import pepver_to_semver
from platformio.system.prune import calculate_unnecessary_system_data
def on_platformio_start(ctx, force, caller): def on_platformio_start(ctx, force, caller):
@ -51,8 +51,8 @@ def on_platformio_end(ctx, result): # pylint: disable=unused-argument
check_platformio_upgrade() check_platformio_upgrade()
check_prune_system() check_prune_system()
except ( except (
http.HTTPClientError, HTTPClientError,
http.InternetIsOffline, InternetIsOffline,
exception.GetLatestVersionError, exception.GetLatestVersionError,
): ):
click.secho( click.secho(
@ -142,8 +142,8 @@ def after_upgrade(ctx):
) )
click.secho("Please remove multiple PIO Cores from a system:", fg="yellow") click.secho("Please remove multiple PIO Cores from a system:", fg="yellow")
click.secho( click.secho(
"https://docs.platformio.org/page/faq.html" "https://docs.platformio.org/en/latest/core"
"#multiple-platformio-cores-in-a-system", "/installation/troubleshooting.html",
fg="cyan", fg="cyan",
) )
click.secho("*" * terminal_width, fg="yellow") click.secho("*" * terminal_width, fg="yellow")
@ -213,7 +213,7 @@ def check_platformio_upgrade():
if not last_checked_time: if not last_checked_time:
return return
http.ensure_internet_on(raise_exception=True) ensure_internet_on(raise_exception=True)
# Update PlatformIO Core packages # Update PlatformIO Core packages
update_core_packages() update_core_packages()

View File

@ -264,7 +264,8 @@ def _uninstall_project_unused_libdeps(project_env, options):
lm.uninstall(spec) lm.uninstall(spec)
except UnknownPackageError: except UnknownPackageError:
pass pass
storage_dir.mkdir(parents=True, exist_ok=True) if not storage_dir.is_dir():
storage_dir.mkdir(parents=True)
integrity_dat.write_text("\n".join(lib_deps), encoding="utf-8") integrity_dat.write_text("\n".join(lib_deps), encoding="utf-8")

View File

@ -21,14 +21,14 @@ import click
from tabulate import tabulate from tabulate import tabulate
from platformio import fs from platformio import fs
from platformio.clients.account import AccountClient from platformio.account.client import AccountClient
from platformio.clients.registry import RegistryClient
from platformio.exception import UserSideException from platformio.exception import UserSideException
from platformio.package.manifest.parser import ManifestParserFactory from platformio.package.manifest.parser import ManifestParserFactory
from platformio.package.manifest.schema import ManifestSchema from platformio.package.manifest.schema import ManifestSchema
from platformio.package.meta import PackageType from platformio.package.meta import PackageType
from platformio.package.pack import PackagePacker from platformio.package.pack import PackagePacker
from platformio.package.unpack import FileUnpacker, TARArchiver from platformio.package.unpack import FileUnpacker, TARArchiver
from platformio.registry.client import RegistryClient
def validate_datetime(ctx, param, value): # pylint: disable=unused-argument def validate_datetime(ctx, param, value): # pylint: disable=unused-argument

View File

@ -17,7 +17,7 @@ import math
import click import click
from platformio import util from platformio import util
from platformio.clients.registry import RegistryClient from platformio.registry.client import RegistryClient
@click.command("search", short_help="Search for packages") @click.command("search", short_help="Search for packages")

View File

@ -18,10 +18,10 @@ import click
from tabulate import tabulate from tabulate import tabulate
from platformio import fs, util from platformio import fs, util
from platformio.clients.registry import RegistryClient
from platformio.exception import UserSideException from platformio.exception import UserSideException
from platformio.package.manager._registry import PackageManagerRegistryMixin from platformio.package.manager._registry import PackageManagerRegistryMixin
from platformio.package.meta import PackageSpec, PackageType from platformio.package.meta import PackageSpec, PackageType
from platformio.registry.client import RegistryClient
@click.command("show", short_help="Show package information") @click.command("show", short_help="Show package information")

View File

@ -14,9 +14,9 @@
import click import click
from platformio.clients.account import AccountClient from platformio.account.client import AccountClient
from platformio.clients.registry import RegistryClient
from platformio.package.meta import PackageSpec, PackageType from platformio.package.meta import PackageSpec, PackageType
from platformio.registry.client import RegistryClient
@click.command("unpublish", short_help="Remove a pushed package from the registry") @click.command("unpublish", short_help="Remove a pushed package from the registry")

View File

@ -12,97 +12,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
import time import time
from urllib.parse import urlparse
import click import click
from platformio import __registry_mirror_hosts__
from platformio.cache import ContentCache
from platformio.clients.http import HTTPClient
from platformio.clients.registry import RegistryClient
from platformio.package.exception import UnknownPackageError from platformio.package.exception import UnknownPackageError
from platformio.package.meta import PackageSpec from platformio.package.meta import PackageSpec
from platformio.package.version import cast_version_to_semver from platformio.package.version import cast_version_to_semver
from platformio.registry.client import RegistryClient
from platformio.registry.mirror import RegistryFileMirrorIterator
class RegistryFileMirrorIterator(object):
HTTP_CLIENT_INSTANCES = {}
def __init__(self, download_url):
self.download_url = download_url
self._url_parts = urlparse(download_url)
self._mirror = "%s://%s" % (self._url_parts.scheme, self._url_parts.netloc)
self._visited_mirrors = []
def __iter__(self): # pylint: disable=non-iterator-returned
return self
def __next__(self):
cache_key = ContentCache.key_from_args(
"head", self.download_url, self._visited_mirrors
)
with ContentCache("http") as cc:
result = cc.get(cache_key)
if result is not None:
try:
headers = json.loads(result)
return (
headers["Location"],
headers["X-PIO-Content-SHA256"],
)
except (ValueError, KeyError):
pass
http = self.get_http_client()
response = http.send_request(
"head",
self._url_parts.path,
allow_redirects=False,
params=dict(bypass=",".join(self._visited_mirrors))
if self._visited_mirrors
else None,
x_with_authorization=RegistryClient.allowed_private_packages(),
)
stop_conditions = [
response.status_code not in (302, 307),
not response.headers.get("Location"),
not response.headers.get("X-PIO-Mirror"),
response.headers.get("X-PIO-Mirror") in self._visited_mirrors,
]
if any(stop_conditions):
raise StopIteration
self._visited_mirrors.append(response.headers.get("X-PIO-Mirror"))
cc.set(
cache_key,
json.dumps(
{
"Location": response.headers.get("Location"),
"X-PIO-Content-SHA256": response.headers.get(
"X-PIO-Content-SHA256"
),
}
),
"1h",
)
return (
response.headers.get("Location"),
response.headers.get("X-PIO-Content-SHA256"),
)
def get_http_client(self):
if self._mirror not in RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES:
endpoints = [self._mirror]
for host in __registry_mirror_hosts__:
endpoint = f"https://dl.{host}"
if endpoint not in endpoints:
endpoints.append(endpoint)
RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror] = HTTPClient(
endpoints
)
return RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror]
class PackageManagerRegistryMixin(object): class PackageManagerRegistryMixin(object):

View File

@ -21,7 +21,7 @@ import click
import semantic_version import semantic_version
from platformio import fs, util from platformio import fs, util
from platformio.commands import PlatformioCLI from platformio.cli import PlatformioCLI
from platformio.compat import ci_strings_are_equal from platformio.compat import ci_strings_are_equal
from platformio.package.exception import ManifestException, MissingPackageManifestError from platformio.package.exception import ManifestException, MissingPackageManifestError
from platformio.package.lockfile import LockFile from platformio.package.lockfile import LockFile

View File

@ -15,7 +15,7 @@
import os import os
from platformio import util from platformio import util
from platformio.clients.http import HTTPClientError, InternetIsOffline from platformio.http import HTTPClientError, InternetIsOffline
from platformio.package.exception import UnknownPackageError from platformio.package.exception import UnknownPackageError
from platformio.package.manager.base import BasePackageManager from platformio.package.manager.base import BasePackageManager
from platformio.package.manager.core import get_installed_core_packages from platformio.package.manager.core import get_installed_core_packages

View File

@ -21,8 +21,8 @@ import tarfile
from urllib.parse import urlparse from urllib.parse import urlparse
from platformio import util from platformio import util
from platformio.clients.http import fetch_remote_content
from platformio.compat import get_object_members, string_types from platformio.compat import get_object_members, string_types
from platformio.http import fetch_remote_content
from platformio.package.exception import ManifestParserError, UnknownManifestError from platformio.package.exception import ManifestParserError, UnknownManifestError
from platformio.project.helpers import is_platformio_project from platformio.project.helpers import is_platformio_project

View File

@ -22,7 +22,7 @@ import requests
import semantic_version import semantic_version
from marshmallow import Schema, ValidationError, fields, validate, validates from marshmallow import Schema, ValidationError, fields, validate, validates
from platformio.clients.http import fetch_remote_content from platformio.http import fetch_remote_content
from platformio.package.exception import ManifestValidationError from platformio.package.exception import ManifestValidationError
from platformio.util import memoized from platformio.util import memoized

View File

@ -15,7 +15,6 @@
import os import os
import re import re
import subprocess import subprocess
import sys
from urllib.parse import urlparse from urllib.parse import urlparse
from platformio import proc from platformio import proc
@ -47,12 +46,12 @@ class VCSClientFactory(object):
if not type_: if not type_:
raise VCSBaseException("VCS: Unknown repository type %s" % remote_url) raise VCSBaseException("VCS: Unknown repository type %s" % remote_url)
try: try:
obj = getattr(sys.modules[__name__], "%sClient" % type_.capitalize())( obj = globals()["%sClient" % type_.capitalize()](
src_dir, remote_url, tag, silent src_dir, remote_url, tag, silent
) )
assert isinstance(obj, VCSClientBase) assert isinstance(obj, VCSClientBase)
return obj return obj
except (AttributeError, AssertionError): except (KeyError, AssertionError):
raise VCSBaseException("VCS: Unknown repository type %s" % remote_url) raise VCSBaseException("VCS: Unknown repository type %s" % remote_url)

View File

@ -14,11 +14,12 @@
import os import os
import re import re
import sys
from platformio import fs from platformio import fs
from platformio.compat import load_python_module from platformio.compat import load_python_module
from platformio.package.meta import PackageItem from platformio.package.meta import PackageItem
from platformio.platform.base import PlatformBase from platformio.platform import base
from platformio.platform.exception import UnknownPlatform from platformio.platform.exception import UnknownPlatform
@ -29,14 +30,16 @@ class PlatformFactory(object):
return "%sPlatform" % name.lower().capitalize() return "%sPlatform" % name.lower().capitalize()
@staticmethod @staticmethod
def load_module(name, path): def load_platform_module(name, path):
# backward compatibiility with the legacy dev-platforms
sys.modules["platformio.managers.platform"] = base
try: try:
return load_python_module("platformio.platform.%s" % name, path) return load_python_module("platformio.platform.%s" % name, path)
except ImportError: except ImportError:
raise UnknownPlatform(name) raise UnknownPlatform(name)
@classmethod @classmethod
def new(cls, pkg_or_spec, autoinstall=False) -> PlatformBase: def new(cls, pkg_or_spec, autoinstall=False) -> base.PlatformBase:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manager.platform import PlatformPackageManager
@ -72,16 +75,16 @@ class PlatformFactory(object):
platform_cls = None platform_cls = None
if os.path.isfile(os.path.join(platform_dir, "platform.py")): if os.path.isfile(os.path.join(platform_dir, "platform.py")):
platform_cls = getattr( platform_cls = getattr(
cls.load_module( cls.load_platform_module(
platform_name, os.path.join(platform_dir, "platform.py") platform_name, os.path.join(platform_dir, "platform.py")
), ),
cls.get_clsname(platform_name), cls.get_clsname(platform_name),
) )
else: else:
platform_cls = type( platform_cls = type(
str(cls.get_clsname(platform_name)), (PlatformBase,), {} str(cls.get_clsname(platform_name)), (base.PlatformBase,), {}
) )
_instance = platform_cls(os.path.join(platform_dir, "platform.json")) _instance = platform_cls(os.path.join(platform_dir, "platform.json"))
assert isinstance(_instance, PlatformBase) assert isinstance(_instance, base.PlatformBase)
return _instance return _instance

Some files were not shown because too many files have changed in this diff Show More