Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs sub-commands #4881

Merged
merged 25 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2fba8f6
Add docs sub-commands
mildaniel Mar 16, 2023
7b85f7c
Base command functionality
mildaniel Mar 17, 2023
d70ac6d
Format help text
mildaniel Mar 17, 2023
acff1e2
Format logic
mildaniel Mar 17, 2023
b5e7369
Add docstrings, type hints and remaining docs pages
mildaniel Mar 20, 2023
704ce46
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 20, 2023
010b262
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 20, 2023
a5845c2
Fix existing tests
mildaniel Mar 20, 2023
4548bb3
Merge branch 'docs-sub-commands' of github.com:mildaniel/aws-sam-cli …
mildaniel Mar 20, 2023
f3f9e46
Fix integration test case
mildaniel Mar 20, 2023
d2b0eb2
Fix integration test case
mildaniel Mar 20, 2023
626aa4b
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 20, 2023
e61024b
Add unit tests
mildaniel Mar 21, 2023
91592e2
Merge branch 'docs-sub-commands' of github.com:mildaniel/aws-sam-cli …
mildaniel Mar 21, 2023
11483d7
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 21, 2023
4a40ac0
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 21, 2023
8abbf8c
Remaining tests
mildaniel Mar 21, 2023
4dbcf33
Merge branch 'docs-sub-commands' of github.com:mildaniel/aws-sam-cli …
mildaniel Mar 21, 2023
b01e831
Add docstrings to constructors
mildaniel Mar 22, 2023
26e9a43
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 22, 2023
f8bcf17
Address comments
mildaniel Mar 23, 2023
81f118d
Merge branch 'develop' into docs-sub-commands
mildaniel Mar 23, 2023
a30660e
Black reformat
mildaniel Mar 23, 2023
a3ded50
Change integration tests to use stderr
mildaniel Mar 23, 2023
5ee9dcb
Fix test cases
mildaniel Mar 25, 2023
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
47 changes: 21 additions & 26 deletions samcli/commands/docs/command.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
"""
CLI command for "docs" command
"""
import click
from typing import Type

from samcli.cli.main import common_options, print_cmdline_args
from samcli.commands._utils.command_exception_handler import command_exception_handler
from samcli.lib.telemetry.metric import track_command
from samcli.lib.utils.version_checker import check_newer_version
from click import Command, command

HELP_TEXT = """Launch the AWS SAM CLI documentation in a browser! This command will
show information about setting up credentials, the
AWS SAM CLI lifecycle and other useful details.
"""
from samcli.cli.main import pass_context
from samcli.commands._utils.command_exception_handler import command_exception_handler
from samcli.commands.docs.command_context import COMMAND_NAME, DocsCommandContext
from samcli.commands.docs.core.command import DocsBaseCommand, DocsSubCommand


@click.command("docs", help=HELP_TEXT)
@common_options
@track_command
@check_newer_version
@print_cmdline_args
@command_exception_handler
def cli():
def create_command() -> Type[Command]:
"""
`sam docs` command entry point
Factory method for creating a Docs command
Returns
-------
Type[Command]
Sub-command class if the command line args include
sub-commands, otherwise returns the base command class
"""
if DocsCommandContext().sub_commands:
return DocsSubCommand
return DocsBaseCommand

# All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing
do_cli() # pragma: no cover


def do_cli():
@command(name=COMMAND_NAME, cls=create_command())
@pass_context
@command_exception_handler
def cli(ctx):
"""
Implementation of the ``cli`` method
`sam docs` command entry point
"""
from samcli.commands.docs.docs_context import DocsContext

with DocsContext() as docs_context:
docs_context.run()
125 changes: 125 additions & 0 deletions samcli/commands/docs/command_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import functools
import os
import sys
from typing import Callable, List

from click import echo

from samcli.cli.main import common_options, print_cmdline_args
from samcli.commands.docs.exceptions import InvalidDocsCommandException
from samcli.lib.docs.browser_configuration import BrowserConfiguration, BrowserConfigurationError
from samcli.lib.docs.documentation import Documentation
from samcli.lib.telemetry.metric import track_command

COMMAND_NAME = "docs"

SUCCESS_MESSAGE = "Documentation page opened in a browser."
ERROR_MESSAGE = "Failed to open a web browser. Use the following link to navigate to the documentation page: {URL}"


class DocsCommandContext:
def get_complete_command_paths(self) -> List[str]:
"""
Get a list of strings representing the fully qualified commands invokable by sam docs

Returns
-------
List[str]
A string list of commands including the base command
"""
return [self.base_command + " " + command for command in self.all_commands]

@property
def command_callback(self) -> Callable[[str], None]:
"""
Returns the callback function as a callable with the sub command string
"""
impl = CommandImplementation(command=self.sub_command_string)
return functools.partial(impl.run_command)

@property
def all_commands(self) -> List[str]:
"""
Returns all the commands from the commands list in the docs config
"""
return list(Documentation.load().keys())

@property
def sub_command_string(self) -> str:
"""
Returns a string representation of the sub-commands
"""
return " ".join(self.sub_commands)

@property
def sub_commands(self) -> List[str]:
"""
Returns the filtered command line arguments after "sam docs"
"""
return self._filter_arguments(sys.argv[2:])

@property
def base_command(self) -> str:
"""
Returns a string representation of the base command (e.g "sam docs")

click.get_current_context().command_path returns the entire command by the time it
gets to the leaf node. We just want "sam docs" so we extract it from that string
"""
return f"sam {COMMAND_NAME}"

@staticmethod
def _filter_arguments(commands: List[str]) -> List[str]:
"""
Take a list of command line arguments and filter out all flags

Parameters
----------
commands: List[str]
The command line arguments

Returns
-------
List of strings after filtering it all flags

"""
return list(filter(lambda arg: not arg.startswith("-"), commands))


class CommandImplementation:
def __init__(self, command: str):
"""
Constructor used for instantiating a command implementation object

Parameters
----------
command: str
Name of the command that is being executed
"""
self.command = command
self.docs_command = DocsCommandContext()

@track_command
@print_cmdline_args
@common_options
def run_command(self):
"""
Run the necessary logic for the `sam docs` command

Raises
------
InvalidDocsCommandException
"""
if self.docs_command.sub_commands and self.command not in self.docs_command.all_commands:
raise InvalidDocsCommandException(
f"Command not found. Try using one of the following available commands:{os.linesep}"
f"{os.linesep.join([command for command in self.docs_command.get_complete_command_paths()])}"
)
browser = BrowserConfiguration()
documentation = Documentation(browser=browser, command=self.command)
try:
documentation.open_docs()
except BrowserConfigurationError:
echo(ERROR_MESSAGE.format(URL=documentation.url), err=True)
else:
echo(SUCCESS_MESSAGE)
Empty file.
153 changes: 153 additions & 0 deletions samcli/commands/docs/core/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""
Module contains classes for creating the docs command from click
"""
import os
from typing import List, Optional

from click import Command, Context, MultiCommand, style

from samcli.cli.row_modifiers import RowDefinition
from samcli.commands.docs.command_context import COMMAND_NAME, DocsCommandContext
from samcli.commands.docs.core.formatter import DocsCommandHelpTextFormatter

HELP_TEXT = "NEW! Open the documentation in a browser."
mildaniel marked this conversation as resolved.
Show resolved Hide resolved
DESCRIPTION = """
Launch the AWS SAM CLI documentation in a browser! This command will
show information about setting up credentials, the
AWS SAM CLI lifecycle and other useful details.

The command also be run with sub-commands to open specific pages.
"""


class DocsBaseCommand(Command):
class CustomFormatterContext(Context):
formatter_class = DocsCommandHelpTextFormatter

context_class = CustomFormatterContext

def __init__(self, *args, **kwargs):
"""
Constructor for instantiating a base command for the docs command
"""
self.docs_command = DocsCommandContext()
command_callback = self.docs_command.command_callback
super().__init__(name=COMMAND_NAME, help=HELP_TEXT, callback=command_callback)

@staticmethod
def format_description(formatter: DocsCommandHelpTextFormatter):
"""
Formats the description of the help text for the docs command.

Parameters
----------
formatter: DocsCommandHelpTextFormatter
A formatter instance to use for formatting the help text
"""
with formatter.indented_section(name="Description", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="",
name=DESCRIPTION
+ style(f"{os.linesep} This command does not require access to AWS credentials.", bold=True),
),
],
)

def format_sub_commands(self, formatter: DocsCommandHelpTextFormatter):
"""
Formats the sub-commands of the help text for the docs command.

Parameters
----------
formatter: DocsCommandHelpTextFormatter
A formatter instance to use for formatting the help text
"""
with formatter.indented_section(name="Commands", extra_indents=1):
formatter.write_rd(
[
RowDefinition(self.docs_command.base_command + " " + command)
for command in self.docs_command.all_commands
],
col_max=50,
)

def format_options(self, ctx: Context, formatter: DocsCommandHelpTextFormatter): # type:ignore
"""
Overrides the format_options method from the parent class to update
the help text formatting in a consistent method for the AWS SAM CLI

Parameters
----------
ctx: Context
The click command context
formatter: DocsCommandHelpTextFormatter
A formatter instance to use for formatting the help text
"""
DocsBaseCommand.format_description(formatter)
self.format_sub_commands(formatter)


class DocsSubCommand(MultiCommand):
def __init__(self, command: Optional[List[str]] = None, *args, **kwargs):
"""
Constructor for instantiating a sub-command for the docs command

Parameters
----------
command: Optional[List[str]]
Optional list of strings representing the fully resolved command name (e.g. ["docs", "local", "invoke"])
"""
super().__init__(*args, **kwargs)
self.docs_command = DocsCommandContext()
self.command = command or self.docs_command.sub_commands
self.command_string = self.docs_command.sub_command_string
self.command_callback = self.docs_command.command_callback

def get_command(self, ctx: Context, cmd_name: str) -> Command:
"""
Overriding the get_command method from the parent class.

This method recursively gets creates sub-commands until
it reaches the leaf command, then it returns that as a click command.

Parameters
----------
ctx: Context
The click command context
cmd_name: str
Name of the next command to be added as a sub-command or the leaf command

Returns
-------
Command
Returns either a sub-command to be recursively added to the command tree,
or the leaf command to be invoked by the command handler

"""
next_command = self.command.pop(0)
if not self.command:
return DocsBaseCommand(
name=next_command,
short_help=f"Documentation for {self.command_string}",
callback=self.command_callback,
)
return DocsSubCommand(command=self.command)

def list_commands(self, ctx: Context) -> List[str]:
"""
Overrides the list_command method from the parent class.
Used for the Command class to understand all possible sub-commands.

Parameters
----------
ctx: Context
The click command context

Returns
-------
List[str]
List of strings representing sub-commands callable by the docs command
"""
return self.docs_command.all_commands
15 changes: 15 additions & 0 deletions samcli/commands/docs/core/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Base formatter for the docs command help text
"""
from samcli.cli.formatters import RootCommandHelpTextFormatter
from samcli.cli.row_modifiers import BaseLineRowModifier


class DocsCommandHelpTextFormatter(RootCommandHelpTextFormatter):
def __init__(self, *args, **kwargs):
"""
Constructor for instantiating a formatter object used for formatting help text
"""
super().__init__(*args, **kwargs)
self.left_justification_length = self.width // 2 - self.indent_increment
self.modifiers = [BaseLineRowModifier()]
33 changes: 0 additions & 33 deletions samcli/commands/docs/docs_context.py

This file was deleted.

7 changes: 7 additions & 0 deletions samcli/commands/docs/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from samcli.commands.exceptions import UserException


class InvalidDocsCommandException(UserException):
"""
Exception when the docs command fails
"""
Loading