Commit 7b94726b authored by Andreas Tille's avatar Andreas Tille
Browse files

New upstream version 2019.7.0

parent 5e908400
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -23,9 +23,9 @@ def get_keywords():
    # setup.py/versioneer.py will grep for the variable names, so they must
    # each be defined on a line of their own. _version.py will just call
    # get_keywords().
    git_refnames = " (tag: 2019.4.0)"
    git_full = "dc80fad32777035091692ce1083088380a6ac509"
    git_date = "2019-05-03 04:14:45 +0000"
    git_refnames = " (tag: 2019.7.0)"
    git_full = "06b978c96c8efce8be0c8213e744cb4b389f2bc6"
    git_date = "2019-07-30 18:15:53 +0000"
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
    return keywords

+110 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
# ----------------------------------------------------------------------------

import click

from q2cli.click.command import ToolCommand, ToolGroupCommand


@@ -29,3 +30,112 @@ def dev():
def refresh_cache():
    import q2cli.core.cache
    q2cli.core.cache.CACHE.refresh()


import_theme_help = \
    ("Allows for customization of q2cli's command line styling based on an "
     "imported .theme (INI formatted) file. If you are unfamiliar with .ini "
     "formatted files look here https://en.wikipedia.org/wiki/INI_file."
     "\n"
     "\n"
     "The .theme file allows you to customize text on the basis of what that "
     "text represents with the following supported text types: command, "
     "option, type, default_arg, required, emphasis, problem, warning, error, "
     "and success. These will be your headers in the '[]' brackets. "
     "\n"
     "\n"
     "`command` refers to the name of the command you issued. `option` refers "
     "to the arguments you give to the command when running it. `type` refers "
     "to the QIIME 2 semantic typing of these arguments (where applicable). "
     "`default_arg` refers to the label next to the argument indicating its "
     "default value (where applicable), and if it is required (where "
     "applicable). `required` refers to any arguments that must be passed to "
     "the command for it to work and gives them special formatting on top of "
     "your normal `option` formatting. `emphasis` refers to any emphasized "
     "pieces of text within help text. `problem` refers to the text informing "
     "you there were issues with the command. `warning` refers to the text "
     "for non-fatal issues while `error` refers to the text for fatal issues."
     "`success` refers to text indicating a process completed as expected."
     "\n"
     "\n"
     "Depending on what your terminal supports, some or all of the following "
     "pieces of the text's formatting may be customized: bold, dim (if true "
     "the text's brightness is reduced), underline, blink, reverse (if true "
     "foreground and background colors are reversed), and finally fg "
     "(foreground color) and bg (background color). The first five may each "
     "be either true or false, while the colors may be set to any of the "
     "following: black, red, green, yellow, blue, magenta, cyan, white, "
     "bright_black, bright_red, bright_green, bright_yellow, bright_blue, "
     "bright_magenta, bright_cyan, or bright_white.")


@dev.command(name='import-theme',
             short_help='Install new command line theme.',
             help=import_theme_help,
             cls=ToolCommand)
@click.option('--theme', required=True,
              type=click.Path(exists=True, file_okay=True,
                              dir_okay=False, readable=True),
              help='Path to file containing new theme info')
def import_theme(theme):
    import os
    import shutil
    from configparser import Error

    import q2cli.util
    from q2cli.core.config import CONFIG

    try:
        CONFIG.parse_file(theme)
    except Error as e:
        # If they tried to change [error] in a valid manner before we hit our
        # parsing error, we don't want to use their imported error settings
        CONFIG.styles = CONFIG.get_default_styles()
        header = 'Something went wrong while parsing your theme: '
        q2cli.util.exit_with_error(e, header=header, traceback=None)
    shutil.copy(theme, os.path.join(q2cli.util.get_app_dir(),
                'cli-colors.theme'))


@dev.command(name='export-default-theme',
             short_help='Export the default settings.',
             help='Create a .theme (INI formatted) file from the default '
             'settings at the specified filepath.',
             cls=ToolCommand)
@click.option('--output-path', required=True,
              type=click.Path(exists=False, file_okay=True,
                              dir_okay=False, readable=True),
              help='Path to output the config to')
def export_default_theme(output_path):
    import configparser
    from q2cli.core.config import CONFIG

    parser = configparser.ConfigParser()
    parser.read_dict(CONFIG.get_default_styles())
    with open(output_path, 'w') as fh:
        parser.write(fh)


def abort_if_false(ctx, param, value):
    if not value:
        ctx.abort()


@dev.command(name='reset-theme',
             short_help='Reset command line theme to default.',
             help="Reset command line theme to default. Requres the '--yes' "
             "parameter to be passed asserting you do want to reset.",
             cls=ToolCommand)
@click.option('--yes', is_flag=True, callback=abort_if_false,
              expose_value=False,
              prompt='Are you sure you want to reset your theme?')
def reset_theme():
    import os
    import q2cli.util

    path = os.path.join(q2cli.util.get_app_dir(), 'cli-colors.theme')
    if os.path.exists(path):
        os.unlink(path)
        click.echo('Theme reset.')
    else:
        click.echo('Theme was already default.')
+26 −17
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ def export_data(input_path, output_path, output_format):
    import qiime2.util
    import qiime2.sdk
    import distutils
    from q2cli.core.config import CONFIG
    result = qiime2.sdk.Result.load(input_path)
    if output_format is None:
        if isinstance(result, qiime2.sdk.Artifact):
@@ -56,7 +57,7 @@ def export_data(input_path, output_path, output_format):
    else:
        if isinstance(result, qiime2.sdk.Visualization):
            error = '--output-format cannot be used with visualizations'
            click.secho(error, fg='red', bold=True, err=True)
            click.echo(CONFIG.cfg_style('error', error), err=True)
            click.get_current_context().exit(1)
        else:
            source = result.view(qiime2.sdk.parse_format(output_format))
@@ -73,7 +74,7 @@ def export_data(input_path, output_path, output_format):
    output_type = 'file' if os.path.isfile(output_path) else 'directory'
    success = 'Exported %s as %s to %s %s' % (input_path, output_format,
                                              output_type, output_path)
    click.secho(success, fg='green')
    click.echo(CONFIG.cfg_style('success', success))


def show_importable_types(ctx, param, value):
@@ -147,6 +148,7 @@ def show_importable_formats(ctx, param, value):
def import_data(type, input_path, output_path, input_format):
    import qiime2.sdk
    import qiime2.plugin
    from q2cli.core.config import CONFIG
    try:
        artifact = qiime2.sdk.Artifact.import_data(type, input_path,
                                                   view_type=input_format)
@@ -163,7 +165,7 @@ def import_data(type, input_path, output_path, input_format):
    success = 'Imported %s as %s to %s' % (input_path,
                                           input_format,
                                           output_path)
    click.secho(success, fg='green')
    click.echo(CONFIG.cfg_style('success', success))


@tools.command(short_help='Take a peek at a QIIME 2 Artifact or '
@@ -176,16 +178,17 @@ def import_data(type, input_path, output_path, input_format):
                metavar=_COMBO_METAVAR)
def peek(path):
    import qiime2.sdk
    from q2cli.core.config import CONFIG

    metadata = qiime2.sdk.Result.peek(path)

    click.secho("UUID:        ", fg="green", nl=False)
    click.secho(metadata.uuid)
    click.secho("Type:        ", fg="green", nl=False)
    click.secho(metadata.type)
    click.echo(CONFIG.cfg_style('type', "UUID")+":        ", nl=False)
    click.echo(metadata.uuid)
    click.echo(CONFIG.cfg_style('type', "Type")+":        ", nl=False)
    click.echo(metadata.type)
    if metadata.format is not None:
        click.secho("Data format: ", fg="green", nl=False)
        click.secho(metadata.format)
        click.echo(CONFIG.cfg_style('type', "Data format")+": ", nl=False)
        click.echo(metadata.format)


@tools.command('inspect-metadata',
@@ -274,7 +277,7 @@ def _load_metadata(path):
@tools.command(short_help='View a QIIME 2 Visualization.',
               help="Displays a QIIME 2 Visualization until the command "
                    "exits. To open a QIIME 2 Visualization so it can be "
                    "used after the command exits, use 'qiime extract'.",
                    "used after the command exits, use 'qiime tools extract'.",
               cls=ToolCommand)
@click.argument('visualization-path', metavar='VISUALIZATION',
                type=click.Path(exists=True, file_okay=True, dir_okay=False,
@@ -285,6 +288,7 @@ def _load_metadata(path):
def view(visualization_path, index_extension):
    # Guard headless envs from having to import anything large
    import sys
    from q2cli.core.config import CONFIG
    if not os.getenv("DISPLAY") and sys.platform != "darwin":
        raise click.UsageError(
            'Visualization viewing is currently not supported in headless '
@@ -310,15 +314,16 @@ def view(visualization_path, index_extension):

    if index_extension not in index_paths:
        raise click.BadParameter(
            'No index %s file with is present in the archive. Available index '
            'No index %s file is present in the archive. Available index '
            'extensions are: %s' % (index_extension,
                                    ', '.join(index_paths.keys())))
    else:
        index_path = index_paths[index_extension]
        launch_status = click.launch(index_path)
        if launch_status != 0:
            click.echo('Viewing visualization failed while attempting to '
                       'open %s' % index_path, err=True)
            click.echo(CONFIG.cfg_style('error', 'Viewing visualization '
                                        'failed while attempting to open '
                                        f'{index_path}'), err=True)
        else:
            while True:
                click.echo(
@@ -362,6 +367,7 @@ def view(visualization_path, index_extension):
def extract(input_path, output_path):
    import zipfile
    import qiime2.sdk
    from q2cli.core.config import CONFIG

    try:
        extracted_dir = qiime2.sdk.Result.extract(input_path, output_path)
@@ -371,7 +377,7 @@ def extract(input_path, output_path):
            'Visualizations can be extracted.' % input_path)
    else:
        success = 'Extracted %s to directory %s' % (input_path, extracted_dir)
        click.secho(success, fg='green')
        click.echo(CONFIG.cfg_style('success', success))


@tools.command(short_help='Validate data in a QIIME 2 Artifact.',
@@ -393,6 +399,7 @@ def extract(input_path, output_path):
              default='max', show_default=True)
def validate(path, level):
    import qiime2.sdk
    from q2cli.core.config import CONFIG

    try:
        result = qiime2.sdk.Result.load(path)
@@ -411,8 +418,8 @@ def validate(path, level):
                  'validate result %s:' % path)
        q2cli.util.exit_with_error(e, header=header)
    else:
        click.secho('Result %s appears to be valid at level=%s.'
                    % (path, level), fg="green")
        click.echo(CONFIG.cfg_style('success', f'Result {path} appears to be '
                                    f'valid at level={level}.'))


@tools.command(short_help='Print citations for a QIIME 2 result.',
@@ -425,6 +432,7 @@ def validate(path, level):
def citations(path):
    import qiime2.sdk
    import io
    from q2cli.core.config import CONFIG
    ctx = click.get_current_context()

    try:
@@ -439,5 +447,6 @@ def citations(path):
            click.echo(fh.getvalue(), nl=False)
        ctx.exit(0)
    else:
        click.secho('No citations found.', fg='yellow', err=True)
        click.echo(CONFIG.cfg_style('problem', 'No citations found.'),
                   err=True)
        ctx.exit(1)
+20 −30
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ class BaseCommandMixin:
    #   c6042bf2607c5be22b1efef2e42a94ffd281434c/click/core.py#L934 >
    # Copyright (c) 2014 by the Pallets team.
    def parse_args(self, ctx, args):
        from q2cli.core.config import CONFIG
        if isinstance(self, click.MultiCommand):
            return super().parse_args(ctx, args)

@@ -71,14 +72,15 @@ class BaseCommandMixin:
                problems = 'There were some problems with the command:'
            else:
                problems = 'There was a problem with the command:'
            click.secho(problems.center(78, ' '), fg='yellow', err=True)
            click.echo(CONFIG.cfg_style('problem',
                       problems.center(78, ' ')), err=True)
            for idx, e in enumerate(errors, 1):
                msg = click.formatting.wrap_text(
                    e.format_message(),
                    initial_indent=' (%d/%d%s) ' % (idx, len(errors),
                                                    '?' if skip_rest else ''),
                    subsequent_indent='  ')
                click.secho(msg, err=True, fg='red', bold=True)
                click.secho(CONFIG.cfg_style('error', msg), err=True)
            ctx.exit(1)

        ctx.args = args
@@ -113,12 +115,14 @@ class BaseCommandMixin:
    #   /c6042bf2607c5be22b1efef2e42a94ffd281434c/click/core.py#L830 >
    # Copyright (c) 2014 by the Pallets team.
    def format_usage(self, ctx, formatter):
        from q2cli.core.config import CONFIG
        """Writes the usage line into the formatter."""
        pieces = self.collect_usage_pieces(ctx)
        formatter.write_usage(_style_command(ctx.command_path),
        formatter.write_usage(CONFIG.cfg_style('command', ctx.command_path),
                              ' '.join(pieces))

    def format_options(self, ctx, formatter, COL_MAX=23, COL_MIN=10):
        from q2cli.core.config import CONFIG
        # write options
        opt_groups = {}
        records = []
@@ -167,7 +171,7 @@ class BaseCommandMixin:
            rows = []
            for subcommand, cmd in commands:
                help = cmd.get_short_help_str(limit)
                rows.append((_style_command(subcommand), help))
                rows.append((CONFIG.cfg_style('command', subcommand), help))

            if rows:
                with formatter.section(click.style('Commands', bold=True)):
@@ -175,6 +179,7 @@ class BaseCommandMixin:

    def write_option(self, ctx, formatter, opt, record, border, COL_SPACING=2):
        import itertools
        from q2cli.core.config import CONFIG
        full_width = formatter.width - formatter.current_indent
        indent_text = ' ' * formatter.current_indent
        opt_text, help_text = record
@@ -208,7 +213,8 @@ class BaseCommandMixin:
            for token in tokens:
                dangling_edge += len(token) + 1
                if token.startswith('--'):
                    token = _style_option(token, required=opt.required)
                    token = CONFIG.cfg_style('option', token,
                                             required=opt.required)
                styled.append(token)
            line = indent_text + ' '.join(styled)
            to_write.append(line)
@@ -224,11 +230,11 @@ class BaseCommandMixin:
                line = ' '.join(tokens)
                if first_iter:
                    dangling_edge += 1 + len(line)
                    line = " " + _style_type(line)
                    line = " " + CONFIG.cfg_style('type', line)
                    first_iter = False
                else:
                    dangling_edge = len(type_indent) + len(line)
                    line = type_indent + _style_type(line)
                    line = type_indent + CONFIG.cfg_style('type', line)
                to_write.append(line)
            formatter.write('\n'.join(to_write))

@@ -244,7 +250,8 @@ class BaseCommandMixin:
        if type_placement == 'under':
            padding = ' ' * (border + COL_SPACING
                             - len(type_repr) - len(type_indent))
            line = ''.join([type_indent, _style_type(type_repr), padding])
            line = ''.join(
                [type_indent, CONFIG.cfg_style('type', type_repr), padding])
            left_col.append(line)

        if hasattr(opt, 'meta_help') and opt.meta_help is not None:
@@ -290,10 +297,13 @@ class BaseCommandMixin:
            else:
                pad = formatter.width - len(requirements) - dangling_edge

            formatter.write((' ' * pad) + _style_reqs(requirements) + '\n')
            formatter.write(
                (' ' * pad) + CONFIG.cfg_style(
                    'default_arg', requirements) + '\n')

    def _color_important(self, tokens, ctx):
        import re
        from q2cli.core.config import CONFIG

        for t in tokens:
            if '_' in t:
@@ -301,7 +311,7 @@ class BaseCommandMixin:
                if re.sub(r'[^\w]', '', t) in names:
                    m = re.search(r'(\w+)', t)
                    word = t[m.start():m.end()]
                    word = _style_emphasis(word.replace('_', '-'))
                    word = CONFIG.cfg_style('emphasis', word.replace('_', '-'))
                    token = t[:m.start()] + word + t[m.end():]
                    yield token
                    continue
@@ -353,23 +363,3 @@ def simple_wrap(text, target, start_col=0):
            current_width += 1 + token_len

    return result


def _style_option(text, required=False):
    return click.style(text, fg='blue', underline=required)


def _style_type(text):
    return click.style(text, fg='green')


def _style_reqs(text):
    return click.style(text, fg='magenta')


def _style_command(text):
    return _style_option(text)


def _style_emphasis(text):
    return click.style(text, underline=True)
+27 −4
Original line number Diff line number Diff line
@@ -46,6 +46,10 @@ class OutDirType(click.Path):
        return value


class ControlFlowException(Exception):
    pass


class QIIME2Type(click.ParamType):
    def __init__(self, type_ast, type_repr, is_output=False):
        self.type_repr = type_repr
@@ -94,14 +98,33 @@ class QIIME2Type(click.ParamType):

    def _convert_input(self, value, param, ctx):
        import os
        import tempfile
        import qiime2.sdk
        import qiime2.sdk.util

        try:
            try:
                result = qiime2.sdk.Result.load(value)
            except OSError as e:
                if e.errno == 28:
                    temp = tempfile.tempdir
                    self.fail(f'There was not enough space left on {temp!r} '
                              f'to extract the artifact {value!r}. '
                              '(Try setting $TMPDIR to a directory with '
                              'more space, or increasing the size of '
                              f'{temp!r})', param, ctx)
                else:
                    raise ControlFlowException
            except ValueError as e:
                if 'does not exist' in str(e):
                    self.fail(f'{value!r} is not a valid filepath', param, ctx)
                else:
                    raise ControlFlowException
            except Exception:
            self.fail('%r is not a QIIME 2 Artifact (.qza)' % value,
                      param, ctx)
                raise ControlFlowException
        except ControlFlowException:
            self.fail('%r is not a QIIME 2 Artifact (.qza)' % value, param,
                      ctx)

        if isinstance(result, qiime2.sdk.Visualization):
            maybe = value[:-1] + 'a'
Loading