Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lean/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# limitations under the License.

from lean.commands.lean import lean
from lean.commands.completion import completion
from lean.commands.backtest import backtest
from lean.commands.build import build
from lean.commands.cloud import cloud
Expand All @@ -36,6 +37,7 @@
from lean.commands.private_cloud import private_cloud

lean.add_command(config)
lean.add_command(completion)
lean.add_command(cloud)
lean.add_command(data)
lean.add_command(decrypt)
Expand Down
3 changes: 2 additions & 1 deletion lean/commands/cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# limitations under the License.

from click import group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

from lean.commands.cloud.backtest import backtest
from lean.commands.cloud.live.live import live
Expand All @@ -21,7 +22,7 @@
from lean.commands.cloud.status import status
from lean.commands.cloud.object_store import object_store

@group()
@group(cls=AliasedCommandGroup)
def cloud() -> None:
"""Interact with the QuantConnect cloud."""
# This method is intentionally empty
Expand Down
74 changes: 74 additions & 0 deletions lean/commands/completion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean CLI v1.0. Copyright 2021 QuantConnect Corporation.
#
# 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 typing import Optional

from click import Choice, Context, echo, group, option, pass_context

from lean.components.util.click_aliased_command_group import AliasedCommandGroup
from lean.components.util.click_shell_completion import get_completion_script, install_completion, uninstall_completion


SHELL_OPTION = option("--shell",
"-s",
type=Choice(["powershell", "bash", "zsh", "fish"], case_sensitive=False),
default=None,
help="Target shell. Auto-detected if not specified.")


@group(cls=AliasedCommandGroup, invoke_without_command=True)
@SHELL_OPTION
@pass_context
def completion(ctx: Context, shell: Optional[str]) -> None:
"""Print the native shell completion script for your shell.

\b
PowerShell (current session):
lean completion --shell powershell | Out-String | Invoke-Expression

\b
Bash or Zsh (current session):
eval "$(lean completion --shell bash)"

\b
Fish (current session):
lean completion --shell fish | source
"""
if ctx.invoked_subcommand is None:
echo(get_completion_script(shell))


@completion.command(name="show", help="Print the native shell completion script for your shell")
@SHELL_OPTION
def show(shell: Optional[str]) -> None:
echo(get_completion_script(shell))


@completion.command(name="on", help="Enable shell completion in your shell profile")
@SHELL_OPTION
def on(shell: Optional[str]) -> None:
profile_path = install_completion(shell)
echo(f"Enabled shell completion in {profile_path}")
echo("Open a new terminal session for the change to take effect.")


@completion.command(name="off", help="Disable shell completion in your shell profile")
@SHELL_OPTION
def off(shell: Optional[str]) -> None:
profile_path, removed = uninstall_completion(shell)

if removed:
echo(f"Disabled shell completion in {profile_path}")
echo("Open a new terminal session for the change to take effect.")
else:
echo(f"Shell completion was not enabled in {profile_path}")
3 changes: 2 additions & 1 deletion lean/commands/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
# limitations under the License.

from click import group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

from lean.commands.config.get import get
from lean.commands.config.list import list
from lean.commands.config.set import set
from lean.commands.config.unset import unset


@group()
@group(cls=AliasedCommandGroup)
def config() -> None:
"""Configure Lean CLI options."""
# This method is intentionally empty
Expand Down
3 changes: 2 additions & 1 deletion lean/commands/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
# limitations under the License.

from click import group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

from lean.commands.data.download import download
from lean.commands.data.generate import generate


@group()
@group(cls=AliasedCommandGroup)
def data() -> None:
"""Download or generate data for local use."""
# This method is intentionally empty
Expand Down
1 change: 1 addition & 0 deletions lean/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def init(organization: Optional[str], language: Optional[str]) -> None:
- Synchronizing projects with the cloud: https://www.lean.io/docs/v2/lean-cli/projects/cloud-synchronization

Here are some commands to get you going:
- Run `lean completion --shell powershell | Out-String | Invoke-Expression` to enable PowerShell completion in the current session
- Run `lean create-project "My Project"` to create a new project with starter code
- Run `lean cloud pull` to download all your QuantConnect projects to your local drive
- Run `lean backtest "My Project"` to backtest a project locally with the data in {DEFAULT_DATA_DIRECTORY_NAME}/
Expand Down
3 changes: 3 additions & 0 deletions lean/commands/lean.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
from lean import __version__
from lean.click import verbose_option
from lean.components.util.click_aliased_command_group import AliasedCommandGroup
from lean.components.util.click_shell_completion import register_shell_completion
from lean.container import container
from lean.models.errors import MoreInfoError

register_shell_completion()


@group(cls=AliasedCommandGroup, invoke_without_command=True)
@option("--version", is_flag=True, is_eager=True, help="Show the version and exit.")
Expand Down
3 changes: 2 additions & 1 deletion lean/commands/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
# limitations under the License.

from click import group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

from lean.commands.library.add import add
from lean.commands.library.remove import remove


@group()
@group(cls=AliasedCommandGroup)
def library() -> None:
"""Manage custom libraries in a project."""
# This method is intentionally empty
Expand Down
3 changes: 2 additions & 1 deletion lean/commands/private_cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
# limitations under the License.

from click import group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

from lean.commands.private_cloud.start import start
from lean.commands.private_cloud.stop import stop
from lean.commands.private_cloud.add_compute import add_compute


@group()
@group(cls=AliasedCommandGroup)
def private_cloud() -> None:
"""Interact with a QuantConnect private cloud."""
# This method is intentionally empty
Expand Down
42 changes: 31 additions & 11 deletions lean/components/util/click_aliased_command_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,49 @@


class AliasedCommandGroup(Group):
"""A click.Group wrapper that implements command aliasing."""
"""A click.Group wrapper that implements command aliasing and auto-completion/prefix matching."""

def get_command(self, ctx, cmd_name):
rv = super().get_command(ctx, cmd_name)
if rv is not None:
return rv

matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]

if not matches:
return None
elif len(matches) == 1:
return super().get_command(ctx, matches[0])

ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
Comment thread
shreejaykurhade marked this conversation as resolved.

def command(self, *args, **kwargs):
aliases = kwargs.pop('aliases', [])

if not args:
cmd_name = kwargs.pop("name", "")
else:
cmd_name = args[0]
args = args[1:]

alias_help = f"Alias for '{cmd_name}'"
if not aliases:
return super().command(*args, **kwargs)

def _decorator(f):
if args:
cmd_name = args[0]
cmd_args = args[1:]
else:
cmd_name = kwargs.get("name", f.__name__.lower().replace("_", "-"))
cmd_args = ()

alias_help = f"Alias for '{cmd_name}'"
cmd_kwargs = dict(kwargs)
cmd_kwargs.pop("name", None)

# Add the main command
cmd = super(AliasedCommandGroup, self).command(name=cmd_name, *args, **kwargs)(f)
cmd = super(AliasedCommandGroup, self).command(*cmd_args, name=cmd_name, **cmd_kwargs)(f)

# Add a command to the group for each alias with the same callback but using the alias as name
for alias in aliases:
alias_cmd = super(AliasedCommandGroup, self).command(name=alias,
short_help=alias_help,
*args,
**kwargs)(f)
*cmd_args,
**cmd_kwargs)(f)
alias_cmd.params = cmd.params

return cmd
Expand Down
4 changes: 2 additions & 2 deletions lean/components/util/click_group_default_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from click import Group
from lean.components.util.click_aliased_command_group import AliasedCommandGroup

class DefaultCommandGroup(Group):
class DefaultCommandGroup(AliasedCommandGroup):
"""allow a default command for a group"""

def command(self, *args, **kwargs):
Expand Down
Loading