<gh_stars>10-100
import typing
from argparse import ArgumentParser

import cauldron
from cauldron import cli
from cauldron.cli import sync
from cauldron.cli.commands import sync as sync_command
from cauldron.cli.commands.open import opener as project_opener
from cauldron.cli.commands.steps import actions
from cauldron.cli.commands.steps import removal
from cauldron.cli.commands.steps import selection
from cauldron.cli.interaction import autocompletion
from cauldron.environ import Response
from cauldron.session import projects

NAME = 'steps'
DESCRIPTION = """
    Carry out an action on one or more steps within the currently opened
    project. The available actions are:
        * [add]: Creates a new step
        * [list]: Lists the steps within the currently opened project
        * [modify]: Modifies an existing step
        * [remove]: Removes an existing step from the project
        * [unmute]: Enables a step within the active project
        * [mute]: Disables a step within the active project
    """


def populate(
        parser: ArgumentParser,
        raw_args: typing.List[str],
        assigned_args: dict
):
    """..."""
    if len(raw_args) < 1:
        assigned_args['action'] = 'list'
        return

    action = raw_args.pop(0).lower()
    assigned_args['action'] = action

    if action == 'add':
        parser.add_argument(
            'step_name',
            type=str,
            nargs='?',
            help=cli.reformat(
                """
                The name of the step you want to create
                """
            )
        )
    elif action not in ['list', 'clean']:
        parser.add_argument(
            'step_name',
            type=str,
            help=cli.reformat(
                """
                The name of the step on which to carry out the steps action
                """
            )
        )

    if action in ['mute', 'unmute', 'select']:
        return

    if action in ['add', 'modify']:
        parser.add_argument(
            '-p', '--position',
            dest='position',
            type=str,
            default=None,
            help=cli.reformat(
                """
                Specifies the index where the step will be inserted, or the
                name of the step after which this new step will be inserted.
                """
            )
        )

        parser.add_argument(
            '-t', '--title',
            dest='title',
            type=str,
            default=None,
            help=cli.reformat(
                """
                This specifies the title for the step that will be added or
                modified
                """
            )
        )

    if action == 'modify':
        parser.add_argument(
            '-n', '--name',
            dest='new_name',
            type=str,
            default=None,
            help=cli.reformat(
                """
                This new name for the step when modifying an existing one
                """
            )
        )

    if action == 'remove':
        parser.add_argument(
            '-k', '--keep',
            dest='keep',
            default=False,
            action='store_true',
            help=cli.reformat(
                """
                Whether or not to keep the source file when removing a step
                from a project
                """
            )
        )


def execute_remote(
        context: cli.CommandContext,
        action: str = None,
        step_name: str = None,
        position: str = None,
        title: str = None,
        new_name: str = None,
        keep: bool = False,
) -> Response:
    """..."""
    # modification_start_timestamp = time.time()

    if action in ['list', 'clean', 'select']:
        thread = sync.send_remote_command(
            command=context.name,
            raw_args=context.raw_args,
            asynchronous=False
        )
        thread.join()
        response = thread.responses[0]
        return context.response.consume(response)

    status_response = sync.comm.send_request(
        endpoint='/sync-status',
        remote_connection=context.remote_connection
    )
    if status_response.failed:
        return context.response.consume(status_response)

    source_directory = status_response.data['remote_source_directory']
    if not project_opener.project_exists(context.response, source_directory):
        return context.response

    context.response.consume(execute(
        context=context,
        action=action,
        step_name=step_name,
        position=position,
        title=title,
        new_name=new_name,
        keep=keep,
        project=projects.Project(source_directory)
    ))
    if context.response.failed:
        return context.response

    sync_response = sync_command.execute(cli.make_command_context(
        name=sync_command.NAME,
        remote_connection=context.remote_connection
    ))
    sync_response.join()

    return context.response.consume(sync_response)


def execute(
        context: cli.CommandContext,
        action: str = None,
        step_name: str = None,
        position: str = None,
        title: str = None,
        new_name: str = None,
        keep: bool = False,
        project: 'projects.Project' = None
) -> Response:
    """..."""
    response = context.response

    project = (
        project
        if project else
        cauldron.project.get_internal_project()
    )

    if not project:
        return response.fail(
            code='NO_OPEN_PROJECT',
            message='No project is open. Step commands require an open project'
        ).console(
            whitespace=1
        ).response

    if not action or action == 'list':
        actions.echo_steps(response, project)
        return response

    if action == 'clean':
        return actions.clean_steps(response, project)

    if action == 'add' and not step_name:
            step_name = ''
    elif not step_name:
        return response.fail(
            code='NO_STEP_NAME',
            message='A step name is required for this command'
        ).console(
            whitespace=1
        ).response

    step_name = step_name.strip('"')

    if action == 'add':
        return actions.create_step(
            response=response,
            project=project,
            name=step_name,
            position=position,
            title=title.strip('"') if title else title
        )

    if action == 'modify':
        return actions.modify_step(
            response=response,
            project=project,
            name=step_name,
            new_name=new_name,
            title=title,
            position=position
        )

    if action == 'remove':
        return removal.remove_step(
            response=response,
            project=project,
            name=step_name,
            keep_file=keep
        )

    if action == 'unmute':
        actions.toggle_muting(
            response=response,
            project=project,
            step_name=step_name,
            value=False
        )
        return response

    if action == 'mute':
        actions.toggle_muting(
            response=response,
            project=project,
            step_name=step_name,
            value=True
        )
        return response

    if action == 'select':
        return selection.select_step(
            response=response,
            project=project,
            step_name=step_name
        )


def autocomplete(segment: str, line: str, parts: typing.List[str]):
    """

    :param segment:
    :param line:
    :param parts:
    :return:
    """
    action_names = [
        'add',
        'list',
        'remove',
        'modify',
        'select',
        'unmute',
        'mute',
        'clean'
    ]

    if len(parts) < 2:
        return autocompletion.matches(segment, parts[0], action_names)

    action = parts[0]
    if action == 'list':
        return []

    project = cauldron.project.internal_project

    if len(parts) < 3 or parts[-1].startswith(('--position=', '-p ')):
        prefix = parts[-1]
        for remove in ['--position=', '-p ']:
            if prefix.startswith(remove):
                prefix = prefix[len(remove):]
                break
        prefix = prefix.strip().strip('"')

        step_names = [x.definition.name for x in project.steps]
        return autocompletion.match_in_path_list(
            segment,
            prefix,
            step_names
        )

    if parts[-1].startswith('-'):
        if action == 'list':
            return []

        shorts = []
        longs = []

        if action == 'remove':
            shorts.append('k')
            longs.append('keep')
        else:
            shorts += ['p', 't']
            longs += ['position=', 'title=']

        if action == 'modify':
            shorts.append('n')
            longs.append('name=')

        return autocompletion.match_flags(
            segment=segment,
            value=parts[-1],
            shorts=shorts,
            longs=longs
        )

    return []



import typing
from argparse import ArgumentParser

import cauldron
from cauldron import cli
from cauldron.cli import sync
from cauldron.cli.commands import sync as sync_command
from cauldron.cli.commands.open import opener as project_opener
from cauldron.cli.commands.steps import actions
from cauldron.cli.commands.steps import removal
from cauldron.cli.commands.steps import selection
from cauldron.cli.interaction import autocompletion
from cauldron.environ import Response
from cauldron.session import projects

NAME = 'steps'
DESCRIPTION = """
    Carry out an action on one or more steps within the currently opened
    project. The available actions are:
        * [add]: Creates a new step
        * [list]: Lists the steps within the currently opened project
        * [modify]: Modifies an existing step
        * [remove]: Removes an existing step from the project
        * [unmute]: Enables a step within the active project
        * [mute]: Disables a step within the active project
    """


def populate(
        parser: ArgumentParser,
        raw_args: typing.List[str],
        assigned_args: dict
):
    """..."""
    if len(raw_args) < 1:
        assigned_args['action'] = 'list'
        return

    action = raw_args.pop(0).lower()
    assigned_args['action'] = action

    if action == 'add':
        parser.add_argument(
            'step_name',
            type=str,
            nargs='?',
            help=cli.reformat(
                """
                The name of the step you want to create
                """
            )
        )
    elif action not in ['list', 'clean']:
        parser.add_argument(
            'step_name',
            type=str,
            help=cli.reformat(
                """
                The name of the step on which to carry out the steps action
                """
            )
        )

    if action in ['mute', 'unmute', 'select']:
        return

    if action in ['add', 'modify']:
        parser.add_argument(
            '-p', '--position',
            dest='position',
            type=str,
            default=None,
            help=cli.reformat(
                """
                Specifies the index where the step will be inserted, or the
                name of the step after which this new step will be inserted.
                """
            )
        )

        parser.add_argument(
            '-t', '--title',
            dest='title',
            type=str,
            default=None,
            help=cli.reformat(
                """
                This specifies the title for the step that will be added or
                modified
                """
            )
        )

    if action == 'modify':
        parser.add_argument(
            '-n', '--name',
            dest='new_name',
            type=str,
            default=None,
            help=cli.reformat(
                """
                This new name for the step when modifying an existing one
                """
            )
        )

    if action == 'remove':
        parser.add_argument(
            '-k', '--keep',
            dest='keep',
            default=False,
            action='store_true',
            help=cli.reformat(
                """
                Whether or not to keep the source file when removing a step
                from a project
                """
            )
        )


def execute_remote(
        context: cli.CommandContext,
        action: str = None,
        step_name: str = None,
        position: str = None,
        title: str = None,
        new_name: str = None,
        keep: bool = False,
) -> Response:
    """..."""
    # modification_start_timestamp = time.time()

    if action in ['list', 'clean', 'select']:
        thread = sync.send_remote_command(
            command=context.name,
            raw_args=context.raw_args,
            asynchronous=False
        )
        thread.join()
        response = thread.responses[0]
        return context.response.consume(response)

    status_response = sync.comm.send_request(
        endpoint='/sync-status',
        remote_connection=context.remote_connection
    )
    if status_response.failed:
        return context.response.consume(status_response)

    source_directory = status_response.data['remote_source_directory']
    if not project_opener.project_exists(context.response, source_directory):
        return context.response

    context.response.consume(execute(
        context=context,
        action=action,
        step_name=step_name,
        position=position,
        title=title,
        new_name=new_name,
        keep=keep,
        project=projects.Project(source_directory)
    ))
    if context.response.failed:
        return context.response

    sync_response = sync_command.execute(cli.make_command_context(
        name=sync_command.NAME,
        remote_connection=context.remote_connection
    ))
    sync_response.join()

    return context.response.consume(sync_response)


def execute(
        context: cli.CommandContext,
        action: str = None,
        step_name: str = None,
        position: str = None,
        title: str = None,
        new_name: str = None,
        keep: bool = False,
        project: 'projects.Project' = None
) -> Response:
    """..."""
    response = context.response

    project = (
        project
        if project else
        cauldron.project.get_internal_project()
    )

    if not project:
        return response.fail(
            code='NO_OPEN_PROJECT',
            message='No project is open. Step commands require an open project'
        ).console(
            whitespace=1
        ).response

    if not action or action == 'list':
        actions.echo_steps(response, project)
        return response

    if action == 'clean':
        return actions.clean_steps(response, project)

    if action == 'add' and not step_name:
            step_name = ''
    elif not step_name:
        return response.fail(
            code='NO_STEP_NAME',
            message='A step name is required for this command'
        ).console(
            whitespace=1
        ).response

    step_name = step_name.strip('"')

    if action == 'add':
        return actions.create_step(
            response=response,
            project=project,
            name=step_name,
            position=position,
            title=title.strip('"') if title else title
        )

    if action == 'modify':
        return actions.modify_step(
            response=response,
            project=project,
            name=step_name,
            new_name=new_name,
            title=title,
            position=position
        )

    if action == 'remove':
        return removal.remove_step(
            response=response,
            project=project,
            name=step_name,
            keep_file=keep
        )

    if action == 'unmute':
        actions.toggle_muting(
            response=response,
            project=project,
            step_name=step_name,
            value=False
        )
        return response

    if action == 'mute':
        actions.toggle_muting(
            response=response,
            project=project,
            step_name=step_name,
            value=True
        )
        return response

    if action == 'select':
        return selection.select_step(
            response=response,
            project=project,
            step_name=step_name
        )


def autocomplete(segment: str, line: str, parts: typing.List[str]):
    """

    :param segment:
    :param line:
    :param parts:
    :return:
    """
    action_names = [
        'add',
        'list',
        'remove',
        'modify',
        'select',
        'unmute',
        'mute',
        'clean'
    ]

    if len(parts) < 2:
        return autocompletion.matches(segment, parts[0], action_names)

    action = parts[0]
    if action == 'list':
        return []

    project = cauldron.project.internal_project

    if len(parts) < 3 or parts[-1].startswith(('--position=', '-p ')):
        prefix = parts[-1]
        for remove in ['--position=', '-p ']:
            if prefix.startswith(remove):
                prefix = prefix[len(remove):]
                break
        prefix = prefix.strip().strip('"')

        step_names = [x.definition.name for x in project.steps]
        return autocompletion.match_in_path_list(
            segment,
            prefix,
            step_names
        )

    if parts[-1].startswith('-'):
        if action == 'list':
            return []

        shorts = []
        longs = []

        if action == 'remove':
            shorts.append('k')
            longs.append('keep')
        else:
            shorts += ['p', 't']
            longs += ['position=', 'title=']

        if action == 'modify':
            shorts.append('n')
            longs.append('name=')

        return autocompletion.match_flags(
            segment=segment,
            value=parts[-1],
            shorts=shorts,
            longs=longs
        )

    return []
Carry out an action on one or more steps within the currently opened     project. The available actions are:         * [add]: Creates a new step         * [list]: Lists the steps within the currently opened project         * [modify]: Modifies an existing step         * [remove]: Removes an existing step from the project         * [unmute]: Enables a step within the active project         * [mute]: Disables a step within the active project ... The name of the step you want to create The name of the step on which to carry out the steps action Specifies the index where the step will be inserted, or the                 name of the step after which this new step will be inserted. This specifies the title for the step that will be added or                 modified This new name for the step when modifying an existing one Whether or not to keep the source file when removing a step                 from a project ... # modification_start_timestamp = time.time() ... :param segment:     :param line:     :param parts:     :return: