Permalink
Browse files

Review command-line handling framework toward consistency and extension.

 - Implement a wrapper around ``click.option`` that handles global config consistently without having to deal with callbacks directly in ``planemo.options``.
 - Implement tracking of how options are set (command-line, global config, or default).
 - Replace command decorator ``pass_context`` with a new decorator that performs the context pass but provides an entry point for more sophisticated command-line hanlding in the future (will use for Galaxy profiles).
 - Improvements to docstrings.
 - Allow new options to be globally configured with defaults in ~/.planemo.yml including afew conda options.
 - Add aenum as a project dependency for tracking option sources.
  • Loading branch information...
jmchilton committed Apr 12, 2016
1 parent fa7b743 commit e769118452edbb017c9167ff0a5c18cc350afcef
Showing with 289 additions and 184 deletions.
  1. +45 −5 planemo/cli.py
  2. +2 −2 planemo/commands/cmd_brew.py
  3. +2 −2 planemo/commands/cmd_brew_env.py
  4. +2 −2 planemo/commands/cmd_brew_init.py
  5. +2 −2 planemo/commands/cmd_conda_env.py
  6. +2 −2 planemo/commands/cmd_conda_init.py
  7. +3 −4 planemo/commands/cmd_conda_install.py
  8. +2 −2 planemo/commands/cmd_create_gist.py
  9. +2 −2 planemo/commands/cmd_cwl_run.py
  10. +2 −2 planemo/commands/cmd_dependency_script.py
  11. +3 −3 planemo/commands/cmd_docker_build.py
  12. +2 −2 planemo/commands/cmd_docker_shell.py
  13. +2 −2 planemo/commands/cmd_docs.py
  14. +2 −2 planemo/commands/cmd_lint.py
  15. +2 −2 planemo/commands/cmd_normalize.py
  16. +2 −2 planemo/commands/cmd_project_init.py
  17. +2 −2 planemo/commands/cmd_serve.py
  18. +2 −2 planemo/commands/cmd_share_test.py
  19. +2 −2 planemo/commands/cmd_shed_build.py
  20. +2 −2 planemo/commands/cmd_shed_create.py
  21. +2 −2 planemo/commands/cmd_shed_diff.py
  22. +2 −2 planemo/commands/cmd_shed_download.py
  23. +2 −2 planemo/commands/cmd_shed_init.py
  24. +2 −2 planemo/commands/cmd_shed_lint.py
  25. +2 −2 planemo/commands/cmd_shed_serve.py
  26. +2 −2 planemo/commands/cmd_shed_test.py
  27. +2 −2 planemo/commands/cmd_shed_update.py
  28. +2 −2 planemo/commands/cmd_shed_upload.py
  29. +2 −2 planemo/commands/cmd_syntax.py
  30. +2 −2 planemo/commands/cmd_test.py
  31. +2 −2 planemo/commands/cmd_test_reports.py
  32. +2 −2 planemo/commands/cmd_tool_factory.py
  33. +2 −2 planemo/commands/cmd_tool_init.py
  34. +2 −2 planemo/commands/cmd_travis_before_install.py
  35. +2 −2 planemo/commands/cmd_travis_init.py
  36. +2 −2 planemo/commands/cmd_virtualenv.py
  37. +73 −16 planemo/config.py
  38. +98 −90 planemo/options.py
  39. +1 −0 requirements.txt
@@ -1,3 +1,4 @@
"""The module describes a CLI framework extending ``click``."""
import os
import sys
import traceback
@@ -21,36 +22,62 @@


class Context(object):
"""Describe context of Planemo computation.
Handles cross cutting concerns for Planemo such as verbose log
tracking, the definition of the Planemo workspace (``~/.planemo``),
and the global configuraton defined in ``~/.planemo.yml``.
"""

def __init__(self):
"""Construct a Context object using execution environment."""
self.home = os.getcwd()
self._global_config = None
# Will be set by planemo CLI driver
self.verbose = False
self.planemo_config = None
self.planemo_directory = None
self.option_source = {}

def set_option_source(self, param_name, option_source):
"""Specify how an option was set."""
assert param_name not in self.option_source
self.option_source[param_name] = option_source

def get_option_source(self, param_name):
"""Return OptionSource value indicating how the option was set."""
assert param_name not in self.option_source
return self.option_source[param_name]

@property
def global_config(self):
"""Read Planemo's global configuration.
As defined most simply by ~/.planemo.yml.
"""
if self._global_config is None:
self._global_config = read_global_config(self.planemo_config)
return self._global_config

def log(self, msg, *args):
"""Logs a message to stderr."""
"""Log a message to stderr."""
if args:
msg %= args
click.echo(msg, file=sys.stderr)

def vlog(self, msg, *args, **kwds):
"""Logs a message to stderr only if verbose is enabled."""
"""Log a message to stderr only if verbose is enabled."""
if self.verbose:
self.log(msg, *args)
if kwds.get("exception", False):
traceback.print_exc(file=sys.stderr)

@property
def workspace(self):
"""Create and return Planemo's workspace.
By default this will be ``~/.planemo``.
"""
if not self.planemo_directory:
raise Exception("No planemo workspace defined.")
workspace = self.planemo_directory
@@ -69,6 +96,7 @@ def workspace(self):


def list_cmds():
"""List planemo commands from commands folder."""
rv = []
for filename in os.listdir(cmd_folder):
if filename.endswith('.py') and \
@@ -81,7 +109,7 @@ def list_cmds():
return rv


def name_to_command(name):
def _name_to_command(name):
try:
if sys.version_info[0] == 2:
name = name.encode('ascii', 'replace')
@@ -101,7 +129,16 @@ def list_commands(self, ctx):
def get_command(self, ctx, name):
if name in COMMAND_ALIASES:
name = COMMAND_ALIASES[name]
return name_to_command(name)
return _name_to_command(name)


def command_function(f):
"""Extension point for processing kwds after click callbacks."""
def outer(*args, **kwargs):
# arg_spec = inspect.getargspec(f)
return f(*args, **kwargs)
outer.__doc__ = f.__doc__
return pass_context(outer)


@click.command(cls=PlanemoCLI, context_settings=CONTEXT_SETTINGS)
@@ -117,7 +154,7 @@ def get_command(self, ctx, name):
envvar="PLANEMO_GLOBAL_WORKSPACE",
help="Workspace for planemo.")
@pass_context
def planemo(ctx, config, directory, verbose): # noqa
def planemo(ctx, config, directory, verbose):
"""A command-line toolkit for building tools and workflows for Galaxy.
Check out the full documentation for Planemo online
@@ -129,5 +166,8 @@ def planemo(ctx, config, directory, verbose): # noqa


__all__ = [
"Context",
"list_cmds",
"pass_context",
"planemo",
]
@@ -1,7 +1,7 @@
"""Module describing the planemo ``brew`` command."""
import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from galaxy.tools.loader_directory import load_tool_elements_from_path
@@ -15,7 +15,7 @@
@click.command('brew')
@options.optional_tools_arg()
@options.brew_option()
@pass_context
@command_function
def cli(ctx, path, brew=None):
"""Install tool requirements using brew. (**Experimental**)
@@ -3,7 +3,7 @@
import click
import os

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options
from planemo.io import ps1_for_path

@@ -22,7 +22,7 @@
"--shell",
is_flag=True
)
@pass_context
@command_function
def cli(ctx, path, brew=None, skip_install=False, shell=None):
"""Display commands used to modify environment to inject tool's brew
dependencies.::
@@ -4,15 +4,15 @@
import urllib
from tempfile import mkstemp

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo.io import shell


INSTALL_SCRIPT = "https://raw.github.com/Homebrew/linuxbrew/go/install"


@click.command('brew_init')
@pass_context
@command_function
def cli(ctx):
"""Download linuxbrew install and run it with ruby. Linuxbrew is a fork
of Homebrew (http://brew.sh/linuxbrew/).
@@ -2,7 +2,7 @@
from __future__ import print_function
import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from planemo.io import ps1_for_path
@@ -34,7 +34,7 @@
@options.optional_tools_arg()
@options.conda_target_options()
# @options.skip_install_option() # TODO
@pass_context
@command_function
def cli(ctx, path, **kwds):
"""Source output to activate a conda environment for this tool.
@@ -1,7 +1,7 @@
"""Module describing the planemo ``conda_init`` command."""
import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options
from planemo.conda import build_conda_context

@@ -10,7 +10,7 @@

@click.command('conda_init')
@options.conda_target_options()
@pass_context
@command_function
def cli(ctx, **kwds):
"""Download and install conda.
@@ -1,7 +1,7 @@
"""Module describing the planemo ``conda_install`` command."""
import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo.io import coalesce_return_codes
from planemo import options

@@ -13,10 +13,9 @@
@click.command('conda_install')
@options.optional_tools_arg()
@options.conda_target_options()
@pass_context
@command_function
def cli(ctx, path, **kwds):
"""Install conda packages for tool requirements.
"""
"""Install conda packages for tool requirements."""
conda_context = build_conda_context(**kwds)
return_codes = []
for conda_target in collect_conda_targets(path):
@@ -1,7 +1,7 @@
"""Module describing the planemo ``create_gist`` command."""
import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo.io import info
from planemo import github_util

@@ -24,7 +24,7 @@
default="raw",
help=("Link type to generate for the file.")
)
@pass_context
@command_function
def cli(ctx, path, **kwds):
"""Download a tool repository as a tarball from the tool shed and extract
to the specified directory.
@@ -1,6 +1,6 @@
"""Module describing the planemo ``cwl_run`` command."""
import click
from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options
from planemo import cwl

@@ -19,7 +19,7 @@
"but the CWL reference implementation cwltool and be selected "
"also.")
)
@pass_context
@command_function
def cli(ctx, path, job_path, **kwds):
"""Planemo command for running CWL tools and jobs.
@@ -7,7 +7,7 @@
from xml.etree import ElementTree as ET

from planemo.io import info, error
from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from planemo.shed2tap.base import BasePackage, Dependency
@@ -161,7 +161,7 @@ def process_tool_dependencies_xml(tool_dep, install_handle, env_sh_handle):
@click.command('dependency_script')
@options.shed_realization_options()
@options.dependencies_script_options()
@pass_context
@command_function
def cli(ctx, paths, recursive=False, fail_fast=True, download_cache=None):
"""Prepare a bash shell script to install tool requirements (**Experimental**)
@@ -1,6 +1,6 @@
"""Module describing the planemo ``docker_build`` command."""
import click
from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options
from planemo.io import error

@@ -15,9 +15,9 @@
@options.docker_sudo_option()
@options.docker_sudo_cmd_option()
@options.docker_host_option()
@pass_context
@command_function
def cli(ctx, path=".", dockerfile=None, **kwds):
"""Builds (and optionally caches Docker images) for tool Dockerfiles.
"""Build (and optionally cache Docker images) for tool Dockerfiles.
Loads the tool or tools referenced by ``TOOL_PATH`` (by default all tools
in current directory), and ensures they all reference the same Docker image
@@ -13,7 +13,7 @@
from __future__ import print_function
import click
import os
from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from galaxy.tools.loader import load_tool
@@ -40,7 +40,7 @@
@options.docker_sudo_option()
@options.docker_sudo_cmd_option()
@options.docker_host_option()
@pass_context
@command_function
def cli(ctx, path, **kwds):
"""Launch a shell in the Docker container referenced by the specified
tool. Prints a command to do this the way Galaxy would in job files it
@@ -1,13 +1,13 @@
"""Module describing the planemo ``docs`` command."""
import click

from planemo.cli import pass_context
from planemo.cli import command_function

SYNTAX_URL = "http://planemo.readthedocs.org/en/latest/"


@click.command("syntax")
@pass_context
@command_function
def cli(ctx, **kwds):
"""Open the Planemo documentation in a web browser."""
click.launch(SYNTAX_URL)
@@ -3,7 +3,7 @@

import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from planemo.tool_lint import build_lint_args
@@ -30,7 +30,7 @@
# help="If an sha256sum is available, download the entire file AND validate it.",
# default=False,
# )
@pass_context
@command_function
def cli(ctx, paths, **kwds):
"""Check specified tool(s) for common errors and adherence to best
practices.
@@ -3,7 +3,7 @@

import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options

from galaxy.tools.loader import (
@@ -35,7 +35,7 @@
"best practices as part of this command, this flag will disable "
"that behavior.")
)
@pass_context
@command_function
def cli(ctx, path, expand_macros=False, **kwds):
"""Generate normalized tool XML from input (breaks formatting).
@@ -5,7 +5,7 @@

import click

from planemo.cli import pass_context
from planemo.cli import command_function
from planemo import options
from planemo.io import (
warn,
@@ -25,7 +25,7 @@
'--template',
default=None
)
@pass_context
@command_function
def cli(ctx, path, template=None, **kwds):
"""Initialize a new tool project (demo only right now).
"""
Oops, something went wrong.

1 comment on commit e769118

@jmchilton

This comment has been minimized.

Copy link
Member Author

jmchilton commented on e769118 May 11, 2016

I meant "Revise command-line handling framework toward consistency and extension."

Please sign in to comment.