[med-svn] [Git][med-team/q2cli][master] 6 commits: New upstream version 2019.7.0
Andreas Tille
gitlab at salsa.debian.org
Fri Aug 2 22:31:34 BST 2019
Andreas Tille pushed to branch master at Debian Med / q2cli
Commits:
7b94726b by Andreas Tille at 2019-08-02T21:13:12Z
New upstream version 2019.7.0
- - - - -
8fd4d7d7 by Andreas Tille at 2019-08-02T21:13:13Z
Update upstream source from tag 'upstream/2019.7.0'
Update to upstream version '2019.7.0'
with Debian dir bec78e88752ce14c38d1e92d0684d159d08054a8
- - - - -
b0675754 by Andreas Tille at 2019-08-02T21:13:13Z
New upstream version
- - - - -
104cb02b by Andreas Tille at 2019-08-02T21:13:13Z
debhelper-compat 12
- - - - -
72e2647d by Andreas Tille at 2019-08-02T21:13:16Z
Standards-Version: 4.4.0
- - - - -
9ae2d5f2 by Andreas Tille at 2019-08-02T21:30:56Z
TODO: Needs most probably qiime (>= 2019.7.0)
- - - - -
16 changed files:
- debian/changelog
- − debian/compat
- debian/control
- q2cli/_version.py
- q2cli/builtin/dev.py
- q2cli/builtin/tools.py
- q2cli/click/command.py
- q2cli/click/type.py
- q2cli/commands.py
- q2cli/core/cache.py
- q2cli/core/completion.py
- + q2cli/core/config.py
- q2cli/tests/test_cli.py
- q2cli/tests/test_core.py
- + q2cli/tests/test_dev.py
- q2cli/util.py
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,13 @@
+q2cli (2019.7.0-1) UNRELEASED; urgency=medium
+
+ * Team upload.
+ * New upstream version
+ * debhelper-compat 12
+ * Standards-Version: 4.4.0
+ TODO: Needs most probably qiime (>= 2019.7.0)
+
+ -- Andreas Tille <tille at debian.org> Fri, 02 Aug 2019 23:13:13 +0200
+
q2cli (2019.4.0-1) unstable; urgency=medium
* New upstream version
=====================================
debian/compat deleted
=====================================
@@ -1 +0,0 @@
-12
=====================================
debian/control
=====================================
@@ -1,16 +1,16 @@
Source: q2cli
-Section: science
-Priority: optional
Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
Uploaders: Liubov Chuprikova <chuprikovalv at gmail.com>
-Build-Depends: debhelper (>= 12~),
+Section: science
+Priority: optional
+Build-Depends: debhelper-compat (= 12),
dh-python,
python3,
python3-setuptools,
qiime,
python3-click,
python3-nose
-Standards-Version: 4.3.0
+Standards-Version: 4.4.0
Vcs-Browser: https://salsa.debian.org/med-team/q2cli
Vcs-Git: https://salsa.debian.org/med-team/q2cli.git
Homepage: https://qiime2.org/
=====================================
q2cli/_version.py
=====================================
@@ -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
=====================================
q2cli/builtin/dev.py
=====================================
@@ -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.")
+
+
+ at dev.command(name='import-theme',
+ short_help='Install new command line theme.',
+ help=import_theme_help,
+ cls=ToolCommand)
+ at 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'))
+
+
+ at 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)
+ at 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()
+
+
+ at 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)
+ at 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.')
=====================================
q2cli/builtin/tools.py
=====================================
@@ -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)
=====================================
q2cli/click/command.py
=====================================
@@ -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)
=====================================
q2cli/click/type.py
=====================================
@@ -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:
- result = qiime2.sdk.Result.load(value)
- except Exception:
- self.fail('%r is not a QIIME 2 Artifact (.qza)' % value,
- param, ctx)
+ 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:
+ 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'
=====================================
q2cli/commands.py
=====================================
@@ -12,6 +12,8 @@ import q2cli.builtin.dev
import q2cli.builtin.info
import q2cli.builtin.tools
+from q2cli.core.config import CONFIG
+
from q2cli.click.command import BaseCommandMixin
@@ -220,8 +222,13 @@ class ActionCommand(BaseCommandMixin, click.Command):
]
options = [*self._inputs, *self._params, *self._outputs, *self._misc]
+ help_ = [action['description']]
+ if self._get_action().deprecated:
+ help_.append(CONFIG.cfg_style(
+ 'warning', 'WARNING:\n\nThis command is deprecated and will '
+ 'be removed in a future version of this plugin.'))
super().__init__(name, params=options, callback=self,
- short_help=action['name'], help=action['description'])
+ short_help=action['name'], help='\n\n'.join(help_))
def _build_generated_options(self):
import q2cli.click.option
@@ -304,6 +311,15 @@ class ActionCommand(BaseCommandMixin, click.Command):
log = tempfile.NamedTemporaryFile(prefix='qiime2-q2cli-err-',
suffix='.log',
delete=False, mode='w')
+ if action.deprecated:
+ # We don't need to worry about redirecting this, since it should a)
+ # always be shown to the user and b) the framework-originated
+ # FutureWarning will wind up in the log file in quiet mode.
+
+ msg = ('Plugin warning from %s:\n\n%s is deprecated and '
+ 'will be removed in a future version of this plugin.' %
+ (q2cli.util.to_cli_name(self.plugin['name']), self.name))
+ click.echo(CONFIG.cfg_style('warning', msg))
cleanup_logfile = False
try:
=====================================
q2cli/core/cache.py
=====================================
@@ -341,7 +341,8 @@ class DeploymentCache:
type_repr = repr(type)
style = qiime2.sdk.util.interrogate_collection_type(type)
- if not qiime2.sdk.util.is_semantic_type(type):
+ if not qiime2.sdk.util.is_semantic_type(type) and \
+ not qiime2.sdk.util.is_union(type):
if style.style is None:
if style.expr.predicate is not None:
type_repr = repr(style.expr.predicate)
@@ -380,6 +381,8 @@ class DeploymentCache:
metavar = 'METADATA'
elif style.style is not None and style.style != 'simple':
metavar = 'VALUE'
+ elif qiime2.sdk.util.is_union(type):
+ metavar = 'VALUE'
else:
metavar = name_to_var[inner_type.name]
if (metavar == 'NUMBER' and inner_type is not None
=====================================
q2cli/core/completion.py
=====================================
@@ -51,7 +51,8 @@ def write_bash_completion_script(plugins, path):
# Make bash completion script executable:
# http://stackoverflow.com/a/12792002/3776794
st = os.stat(path)
- os.chmod(path, st.st_mode | stat.S_IEXEC)
+ # Set executable bit for user,group,other for root/sudo installs
+ os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def _generate_command_reply(cmd):
=====================================
q2cli/core/config.py
=====================================
@@ -0,0 +1,118 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2016-2019, QIIME 2 development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+import os
+import configparser
+
+import click
+
+import q2cli.util
+
+
+class CLIConfig():
+ path = os.path.join(q2cli.util.get_app_dir(), 'cli-colors.theme')
+ VALID_SELECTORS = frozenset(
+ ['option', 'type', 'default_arg', 'command', 'emphasis', 'problem',
+ 'warning', 'error', 'required', 'success'])
+ VALID_STYLINGS = frozenset(
+ ['fg', 'bg', 'bold', 'dim', 'underline', 'blink', 'reverse'])
+ VALID_COLORS = frozenset(
+ ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
+ 'bright_black', 'bright_red', 'bright_green', 'bright_yellow',
+ 'bright_blue', 'bright_magenta', 'bright_cyan', 'bright_white'])
+ VALID_BOOLEANS = {'true': True,
+ 'false': False,
+ 't': True,
+ 'f': False}
+
+ def __init__(self):
+ if os.path.exists(self.path):
+ self.styles = self.get_editable_styles()
+ try:
+ self.parse_file(self.path)
+ except Exception as e:
+ click.secho(
+ "We encountered the following error when parsing your "
+ f"theme:\n\n{str(e)}\n\nIf you want to use a custom "
+ "theme, please either import a new theme, or reset your "
+ "current theme. If you encountered this message while "
+ "importing a new theme or resetting your current theme, "
+ "ignore it.",
+ fg='yellow')
+ self.styles = self.get_default_styles()
+ else:
+ self.styles = self.get_default_styles()
+
+ def get_default_styles(self):
+ return {'option': {'fg': 'blue'},
+ 'type': {'fg': 'green'},
+ 'default_arg': {'fg': 'magenta'},
+ 'command': {'fg': 'blue'},
+ 'emphasis': {'underline': True},
+ 'problem': {'fg': 'yellow'},
+ 'warning': {'fg': 'yellow', 'bold': True},
+ 'error': {'fg': 'red', 'bold': True},
+ 'required': {'underline': True},
+ 'success': {'fg': 'green'}}
+
+ # This maintains the default colors while getting rid of all the default
+ # styling modifiers so what the user puts in their file is all they'll see
+ def get_editable_styles(self):
+ return {'option': {},
+ 'type': {},
+ 'default_arg': {},
+ 'command': {},
+ 'emphasis': {},
+ 'problem': {},
+ 'warning': {},
+ 'error': {},
+ 'required': {},
+ 'success': {}}
+
+ def _build_error(self, current, valid_list, valid_string):
+ valids = ', '.join(valid_list)
+ raise configparser.Error(f'{current!r} is not a {valid_string}. The '
+ f'{valid_string}s are:\n{valids}')
+
+ def parse_file(self, fp):
+ if os.path.exists(fp):
+ parser = configparser.ConfigParser()
+ parser.read(fp)
+ for selector_user in parser.sections():
+ selector = selector_user.lower()
+ if selector not in self.VALID_SELECTORS:
+ self._build_error(selector_user, self.VALID_SELECTORS,
+ 'valid selector')
+ for styling_user in parser[selector]:
+ styling = styling_user.lower()
+ if styling not in self.VALID_STYLINGS:
+ self._build_error(styling_user, self.VALID_STYLINGS,
+ 'valid styling')
+ val_user = parser[selector][styling]
+ val = val_user.lower()
+ if styling == 'fg' or styling == 'bg':
+ if val not in self.VALID_COLORS:
+ self._build_error(val_user, self.VALID_COLORS,
+ 'valid color')
+ else:
+ if val not in self.VALID_BOOLEANS:
+ self._build_error(val_user, self.VALID_BOOLEANS,
+ 'valid boolean')
+ val = self.VALID_BOOLEANS[val]
+ self.styles[selector][styling] = val
+ else:
+ raise configparser.Error(f'{fp!r} is not a valid filepath.')
+
+ def cfg_style(self, selector, text, required=False):
+ kwargs = self.styles[selector]
+ if required:
+ kwargs = {**self.styles[selector], **self.styles['required']}
+ return click.style(text, **kwargs)
+
+
+CONFIG = CLIConfig()
=====================================
q2cli/tests/test_cli.py
=====================================
@@ -8,8 +8,11 @@
import os.path
import unittest
+import unittest.mock
import tempfile
import shutil
+import click
+import errno
from click.testing import CliRunner
from qiime2 import Artifact, Visualization
@@ -19,6 +22,7 @@ from qiime2.core.testing.util import get_dummy_plugin
from q2cli.builtin.info import info
from q2cli.builtin.tools import tools
from q2cli.commands import RootCommand
+from q2cli.click.type import QIIME2Type
class CliTests(unittest.TestCase):
@@ -282,6 +286,60 @@ class CliTests(unittest.TestCase):
self.assertEqual(result.exit_code, 1)
self.assertIn('Traceback (most recent call last)', result.output)
+ def test_input_conversion(self):
+ obj = QIIME2Type(IntSequence1.to_ast(), repr(IntSequence1))
+
+ with self.assertRaisesRegex(click.exceptions.BadParameter,
+ f'{self.tempdir!r} is not a QIIME 2 '
+ 'Artifact'):
+ obj._convert_input(self.tempdir, None, None)
+
+ with self.assertRaisesRegex(click.exceptions.BadParameter,
+ "'x' is not a valid filepath"):
+ obj._convert_input('x', None, None)
+
+ # This is to ensure the temp in the regex matches the temp used in the
+ # method under test in type.py
+ temp = tempfile.tempdir
+ with unittest.mock.patch('qiime2.sdk.Result.load',
+ side_effect=OSError(errno.ENOSPC,
+ 'No space left on '
+ 'device')):
+ with self.assertRaisesRegex(click.exceptions.BadParameter,
+ f'{temp!r}.*'
+ f'{self.artifact1_path!r}.*'
+ f'{temp!r}'):
+ obj._convert_input(self.artifact1_path, None, None)
+
+ def test_deprecated_help_text(self):
+ qiime_cli = RootCommand()
+ command = qiime_cli.get_command(ctx=None, name='dummy-plugin')
+
+ result = self.runner.invoke(command, ['deprecated-method', '--help'])
+
+ self.assertEqual(result.exit_code, 0)
+ self.assertTrue('WARNING' in result.output)
+ self.assertTrue('deprecated' in result.output)
+
+ def test_run_deprecated_gets_warning_msg(self):
+ qiime_cli = RootCommand()
+ command = qiime_cli.get_command(ctx=None, name='dummy-plugin')
+ output_path = os.path.join(self.tempdir, 'output.qza')
+
+ result = self.runner.invoke(
+ command,
+ ['deprecated-method', '--o-out', output_path, '--verbose'])
+
+ self.assertEqual(result.exit_code, 0)
+ self.assertTrue(os.path.exists(output_path))
+
+ artifact = Artifact.load(output_path)
+
+ # Just make sure that the command ran as expected
+ self.assertEqual(artifact.view(dict), {'foo': '43'})
+
+ self.assertTrue('deprecated' in result.output)
+
class TestOptionalArtifactSupport(unittest.TestCase):
def setUp(self):
=====================================
q2cli/tests/test_core.py
=====================================
@@ -10,6 +10,7 @@ import os.path
import shutil
import tempfile
import unittest
+import configparser
from click.testing import CliRunner
from qiime2 import Artifact
@@ -17,9 +18,11 @@ from qiime2.core.testing.type import IntSequence1
from qiime2.core.testing.util import get_dummy_plugin
import q2cli
+import q2cli.util
import q2cli.builtin.info
import q2cli.builtin.tools
from q2cli.commands import RootCommand
+from q2cli.core.config import CLIConfig
class TestOption(unittest.TestCase):
@@ -28,6 +31,9 @@ class TestOption(unittest.TestCase):
self.runner = CliRunner()
self.tempdir = tempfile.mkdtemp(prefix='qiime2-q2cli-test-temp-')
+ self.parser = configparser.ConfigParser()
+ self.path = os.path.join(q2cli.util.get_app_dir(), 'cli-colors.theme')
+
def tearDown(self):
shutil.rmtree(self.tempdir)
@@ -114,6 +120,64 @@ class TestOption(unittest.TestCase):
self.assertTrue(os.path.exists(output_path))
self.assertEqual(Artifact.load(output_path).view(list), [0, 42, 43])
+ def test_config_expected(self):
+ self.parser['type'] = {'underline': 't'}
+ with open(self.path, 'w') as fh:
+ self.parser.write(fh)
+
+ config = CLIConfig()
+ config.parse_file(self.path)
+
+ self.assertEqual(
+ config.styles['type'], {'underline': True})
+
+ def test_config_bad_selector(self):
+ self.parser['tye'] = {'underline': 't'}
+ with open(self.path, 'w') as fh:
+ self.parser.write(fh)
+
+ config = CLIConfig()
+ with self.assertRaisesRegex(
+ configparser.Error, 'tye.*valid selector.*valid selectors'):
+ config.parse_file(self.path)
+
+ def test_config_bad_styling(self):
+ self.parser['type'] = {'underlined': 't'}
+ with open(self.path, 'w') as fh:
+ self.parser.write(fh)
+
+ config = CLIConfig()
+ with self.assertRaisesRegex(
+ configparser.Error, 'underlined.*valid styling.*valid '
+ 'stylings'):
+ config.parse_file(self.path)
+
+ def test_config_bad_color(self):
+ self.parser['type'] = {'fg': 'purple'}
+ with open(self.path, 'w') as fh:
+ self.parser.write(fh)
+
+ config = CLIConfig()
+ with self.assertRaisesRegex(
+ configparser.Error, 'purple.*valid color.*valid colors'):
+ config.parse_file(self.path)
+
+ def test_config_bad_boolean(self):
+ self.parser['type'] = {'underline': 'g'}
+ with open(self.path, 'w') as fh:
+ self.parser.write(fh)
+
+ config = CLIConfig()
+ with self.assertRaisesRegex(
+ configparser.Error, 'g.*valid boolean.*valid booleans'):
+ config.parse_file(self.path)
+
+ def test_no_file(self):
+ config = CLIConfig()
+ with self.assertRaisesRegex(
+ configparser.Error, "'Path' is not a valid filepath."):
+ config.parse_file('Path')
+
if __name__ == "__main__":
unittest.main()
=====================================
q2cli/tests/test_dev.py
=====================================
@@ -0,0 +1,62 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2016-2019, QIIME 2 development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+import os
+import unittest
+import tempfile
+import configparser
+
+from click.testing import CliRunner
+
+import q2cli.util
+from q2cli.builtin.dev import dev
+
+
+class TestDev(unittest.TestCase):
+ path = os.path.join(q2cli.util.get_app_dir(), 'cli-colors.theme')
+ old_settings = None
+ if os.path.exists(path):
+ old_settings = configparser.ConfigParser()
+ old_settings.read(path)
+
+ def setUp(self):
+ self.parser = configparser.ConfigParser()
+ self.runner = CliRunner()
+ self.tempdir = tempfile.mkdtemp(prefix='qiime2-q2cli-test-temp-')
+ self.generated_config = os.path.join(self.tempdir, 'generated-theme')
+
+ self.config = os.path.join(self.tempdir, 'good-config.ini')
+ self.parser['type'] = {'underline': 't'}
+ with open(self.config, 'w') as fh:
+ self.parser.write(fh)
+
+ def tearDown(self):
+ if self.old_settings is not None:
+ with open(self.path, 'w') as fh:
+ self.old_settings.write(fh)
+
+ def test_import_theme(self):
+ result = self.runner.invoke(
+ dev, ['import-theme', '--theme', self.config])
+ self.assertEqual(result.exit_code, 0)
+
+ def test_export_default_theme(self):
+ result = self.runner.invoke(
+ dev, ['export-default-theme', '--output-path',
+ self.generated_config])
+ self.assertEqual(result.exit_code, 0)
+
+ def test_reset_theme(self):
+ result = self.runner.invoke(
+ dev, ['reset-theme', '--yes'])
+ self.assertEqual(result.exit_code, 0)
+
+ def test_reset_theme_no_yes(self):
+ result = self.runner.invoke(
+ dev, ['reset-theme'])
+ self.assertNotEqual(result.exit_code, 0)
=====================================
q2cli/util.py
=====================================
@@ -42,6 +42,7 @@ def exit_with_error(e, header='An error has been encountered:',
import traceback as tb
import textwrap
import click
+ from q2cli.core.config import CONFIG
footer = [] # footer only exists if traceback is set
tb_file = None
@@ -60,7 +61,7 @@ def exit_with_error(e, header='An error has been encountered:',
tb_file.write('\n')
- click.secho('\n\n'.join(segments), fg='red', bold=True, err=True)
+ click.echo(CONFIG.cfg_style('error', '\n\n'.join(segments)), err=True)
if not footer:
click.echo(err=True) # extra newline to look normal
@@ -150,6 +151,7 @@ def convert_primitive(ast):
def citations_option(get_citation_records):
import click
+ from q2cli.core.config import CONFIG
def callback(ctx, param, value):
if not value or ctx.resilient_parsing:
@@ -169,7 +171,8 @@ def citations_option(get_citation_records):
click.echo(fh.getvalue(), nl=False)
ctx.exit()
else:
- click.secho('No citations found.', fg='yellow', err=True)
+ click.secho(
+ CONFIG.cfg_style('problem', 'No citations found.'), err=True)
ctx.exit(1)
return click.Option(['--citations'], is_flag=True, expose_value=False,
View it on GitLab: https://salsa.debian.org/med-team/q2cli/compare/60d601a67dea427eb79fe6a016d23fbb4817a77e...9ae2d5f21b1ee66e79acb4d7685431c7cf1f9366
--
View it on GitLab: https://salsa.debian.org/med-team/q2cli/compare/60d601a67dea427eb79fe6a016d23fbb4817a77e...9ae2d5f21b1ee66e79acb4d7685431c7cf1f9366
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20190802/888b78a4/attachment-0001.html>
More information about the debian-med-commit
mailing list