Skip to content

Commit

Permalink
feat(shell): adds improvements in repl session
Browse files Browse the repository at this point in the history
This commit introduces `shell` sub-command and deprecates the `repl`
sub-command. Following improvements were made:

* Current Project Prompt
* Reads Prompt Toolkit options from Config
* Adds support for suspending the session
  • Loading branch information
ankitrgadiya authored and wiresurfer committed May 25, 2022
1 parent b38c7a0 commit b7a481e
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 57 deletions.
22 changes: 11 additions & 11 deletions riocli/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from click_help_colors import HelpColorsCommand

from riocli.auth.util import select_project, get_token
from riocli.config import Configuration
from riocli.utils.context import get_root_context


@click.command(
Expand All @@ -27,26 +27,26 @@
help='Email of the Rapyuta.io account')
@click.option('--password', prompt='Password', hide_input=True,
help='Password for the Rapyuta.io account')
def login(email: str, password: str):
@click.pass_context
def login(ctx: click.Context, email: str, password: str):
"""
Log into the Rapyuta.io account using the CLI. This is required to use most of the
functionalities of the CLI.
"""

config = Configuration()
config.data['email_id'] = email
config.data['password'] = password

config.data['auth_token'] = get_token(email, password)
ctx = get_root_context(ctx)
ctx.obj.data['email_id'] = email
ctx.obj.data['password'] = password
ctx.obj.data['auth_token'] = get_token(email, password)

# Save if the file does not already exist
if not config.exists:
if not ctx.obj.exists:
click.echo('Logged in successfully!')
config.save()
ctx.obj.save()
else:
click.echo("[Warning] rio already has a config file present")
click.confirm('Do you want to override the config', abort=True)

select_project(config)
config.save()
select_project(ctx.obj)
ctx.obj.save()
click.echo('Logged in successfully!')
16 changes: 8 additions & 8 deletions riocli/auth/logout.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@


@click.command()
def logout():
@click.pass_context
def logout(ctx: click.Context):
"""
Log out from the Rapyuta.io account using the CLI.
"""

config = Configuration()
if not config.exists:
if not ctx.obj.exists:
return

config.data.pop('auth_token', None)
config.data.pop('password', None)
config.data.pop('email_id', None)
config.data.pop('project_id', None)
config.save()
ctx.obj.data.pop('auth_token', None)
ctx.obj.data.pop('password', None)
ctx.obj.data.pop('email_id', None)
ctx.obj.data.pop('project_id', None)
ctx.obj.save()

click.secho('Logged out successfully!', fg='green')
14 changes: 7 additions & 7 deletions riocli/auth/refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@


@click.command()
def refresh_token():
@click.pass_context
def refresh_token(ctx: click.Context):
"""
Refreshes the authentication Token after it expires
"""

config = Configuration()
email = config.data.get('email_id', None)
password = config.data.get('password', None)
if not config.exists or not email or not password:
email = ctx.obj.data.get('email_id', None)
password = ctx.obj.data.get('password', None)
if not ctx.obj.exists or not email or not password:
raise LoggedOut

config.data['auth_token'] = get_token(email, password)
ctx.obj.data['auth_token'] = get_token(email, password)

config.save()
ctx.obj.save()
click.echo('Token refreshed successfully!')
30 changes: 16 additions & 14 deletions riocli/auth/staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,39 @@
from riocli.auth.login import select_project
from riocli.auth.util import get_token
from riocli.config import Configuration
from riocli.utils.context import get_root_context

_STAGING_ENVIRONMENT_SUBDOMAIN = "apps.okd4v2.okd4beta.rapyuta.io"
_NAMED_ENVIRONMENTS = ["v11", "v12", "v13", "v14", "v15", "qa"]


@click.command('environment', hidden=True)
@click.argument('name', type=str)
def environment(name: str):
@click.pass_context
def environment(ctx: click.Context, name: str):
"""
Sets the Rapyuta.io environment to use (Internal use)
"""

config = Configuration()
ctx = get_root_context(ctx)

if name == 'ga':
config.data.pop('environment', None)
config.data.pop('catalog_host', None)
config.data.pop('core_api_host', None)
config.data.pop('rip_host', None)
ctx.obj.data.pop('environment', None)
ctx.obj.data.pop('catalog_host', None)
ctx.obj.data.pop('core_api_host', None)
ctx.obj.data.pop('rip_host', None)
else:
_configure_environment(config, name)
_configure_environment(ctx.obj, name)

config.data.pop('project_id', None)
email = config.data.get('email_id', None)
password = config.data.get('password', None)
config.save()
ctx.obj.data.pop('project_id', None)
email = ctx.obj.data.get('email_id', None)
password = ctx.obj.data.get('password', None)
ctx.obj.save()

config.data['auth_token'] = get_token(email, password)
ctx.obj.data['auth_token'] = get_token(email, password)

select_project(config)
config.save()
select_project(ctx.obj)
ctx.obj.save()


def _validate_environment(name: str) -> bool:
Expand Down
8 changes: 4 additions & 4 deletions riocli/auth/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@


@click.command()
def status():
@click.pass_context
def status(ctx: click.Context):
"""
Shows the Login status of the CLI
"""

config = Configuration()
if not config.exists:
if not ctx.obj.exists:
click.secho('Logged out 🔒', fg='red')
exit(1)

if 'auth_token' in config.data:
if 'auth_token' in ctx.obj.data:
click.secho('Logged in 🎉', fg='green')
1 change: 1 addition & 0 deletions riocli/auth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def select_project(config: Configuration) -> str:

choice = show_selection(project_map, header='Select the project to activate')
config.data['project_id'] = choice
config.data['project_name'] = project_map[choice]


def get_token(email: str, password: str) -> str:
Expand Down
20 changes: 12 additions & 8 deletions riocli/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@

import click
import rapyuta_io.version
from click import Context
from click_help_colors import HelpColorsGroup
from click_plugins import with_plugins
from click_repl import register_repl
from pkg_resources import iter_entry_points

from riocli.auth import auth
from riocli.build import build
from riocli.completion import completion
from riocli.config import Configuration
from riocli.deployment import deployment
from riocli.device import device
from riocli.marketplace import marketplace
Expand All @@ -33,21 +34,23 @@
from riocli.project import project
from riocli.rosbag import rosbag
from riocli.secret import secret
from riocli.shell import shell, deprecated_repl
from riocli.static_route import static_route


@with_plugins(iter_entry_points('riocli.plugins'))
@click.group(
invoke_without_command=False,
cls=HelpColorsGroup,
help_headers_color='yellow',
help_options_color='green',
help_headers_color="yellow",
help_options_color="green",
)
def cli():
pass
@click.pass_context
def cli(ctx: Context, config: str = None):
ctx.obj = Configuration(filepath=config)


@cli.command('help')
@cli.command("help")
@click.pass_context
def cli_help(ctx):
"""
Expand All @@ -61,7 +64,7 @@ def version():
"""
Version of the CLI/SDK
"""
click.echo("rio {} / SDK {}".format(__version__, rapyuta_io.VERSIONSTR))
click.echo("rio {} / SDK {}".format(__version__, rapyuta_io.__version__))
return


Expand All @@ -77,4 +80,5 @@ def version():
cli.add_command(network)
cli.add_command(completion)
cli.add_command(marketplace)
register_repl(cli)
cli.add_command(shell)
cli.add_command(deprecated_repl)
12 changes: 7 additions & 5 deletions riocli/project/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@
# limitations under the License.
import click

from riocli.config import Configuration
from riocli.project.util import name_to_guid
from riocli.utils.context import get_root_context


@click.command('select')
@click.argument('project-name', type=str)
@name_to_guid
def select_project(project_name: str, project_guid: str) -> None:
@click.pass_context
def select_project(ctx: click.Context, project_name: str, project_guid: str) -> None:
"""
Sets the given project in the CLI context. All other resources use this project to act upon.
"""
config = Configuration()
config.data['project_id'] = project_guid
config.save()
ctx = get_root_context(ctx)
ctx.obj.data['project_id'] = project_guid
ctx.obj.data['project_name'] = project_name
ctx.obj.save()
click.secho('Project {} ({}) is selected!'.format(project_name, project_guid), fg='green')
68 changes: 68 additions & 0 deletions riocli/shell/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2022 Rapyuta Robotics
#
# 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 os

import click
from click_help_colors import HelpColorsCommand
from click_repl import repl
from prompt_toolkit.history import FileHistory, ThreadedHistory

from riocli.config import Configuration
from riocli.shell.prompt import prompt_callback


@click.command(
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
)
@click.pass_context
def shell(ctx: click.Context):
"""
Interactive Shell for Rapyuta.io
"""
start_shell(ctx)


@click.command(
'repl',
cls=HelpColorsCommand,
help_headers_color='yellow',
help_options_color='green',
hidden=True
)
@click.pass_context
def deprecated_repl(ctx: click.Context):
"""
[Deprecated] Use "rio shell" instead
"""
start_shell(ctx)


def start_shell(ctx: click.Context):
prompt_config = _parse_config(ctx.obj)
repl(click.get_current_context(), prompt_kwargs=prompt_config)


def _parse_config(config: Configuration) -> dict:
history_path = os.path.join(click.get_app_dir(config.APP_NAME), "history")
default_prompt_kwargs = {
'history': ThreadedHistory(FileHistory(history_path)),
'message': prompt_callback,
'enable_suspend': True
}

shell_config = config.data.get('shell', {})

return {**default_prompt_kwargs, **shell_config}
19 changes: 19 additions & 0 deletions riocli/shell/prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2022 Rapyuta Robotics
#
# 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


@click.pass_context
def prompt_callback(ctx: click.Context) -> str:
return '{} > '.format(ctx.obj.data['project_name'])
27 changes: 27 additions & 0 deletions riocli/utils/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2022 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from click import Context


def get_root_context(ctx: Context) -> Context:
"""
get_root_context figures out the top-level Context from the given context by walking down the linked-list.
https://click.palletsprojects.com/en/8.0.x/complex/#contexts
"""
while True:
if ctx.parent is None:
return ctx

ctx = ctx.parent

0 comments on commit b7a481e

Please sign in to comment.