[med-svn] [python-qcli] 04/06: New upstream version 0.1.1

Andreas Tille tille at debian.org
Wed Oct 18 13:36:32 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository python-qcli.

commit 04d1d1dcab5fc7928dd790bdc59c7dd3ca7de0c6
Author: Andreas Tille <tille at debian.org>
Date:   Wed Oct 18 15:35:16 2017 +0200

    New upstream version 0.1.1
---
 PKG-INFO                 |  10 ++
 debian/changelog         |  15 --
 debian/compat            |   1 -
 debian/control           |  23 ----
 debian/copyright         |  45 ------
 debian/rules             |  33 -----
 debian/source/format     |   1 -
 debian/watch             |   2 -
 qcli/__init__.py         |  23 ++++
 qcli/option_parsing.py   | 350 +++++++++++++++++++++++++++++++++++++++++++++++
 qcli/test.py             | 178 ++++++++++++++++++++++++
 qcli/util.py             |  57 ++++++++
 scripts/qcli_make_rst    | 243 ++++++++++++++++++++++++++++++++
 scripts/qcli_make_script | 129 +++++++++++++++++
 setup.py                 |  18 +++
 15 files changed, 1008 insertions(+), 120 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..536a5f4
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: qcli
+Version: 0.1.1
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index eb5232f..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,15 +0,0 @@
-python-qcli (0.1.1-1) unstable; urgency=medium
-
-  * New upstream version
-  * Fixed watch file
-  * cme fix dpkg-control
-  * debhelper 10
-  * use --buildsystem=pybuild
-
- -- Andreas Tille <tille at debian.org>  Wed, 07 Dec 2016 12:23:23 +0100
-
-python-qcli (0.1.0-1) unstable; urgency=low
-
-  * Initial release (Closes: #733135)
-
- -- Andreas Tille <tille at debian.org>  Thu, 26 Dec 2013 07:55:53 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 685ae00..0000000
--- a/debian/control
+++ /dev/null
@@ -1,23 +0,0 @@
-Source: python-qcli
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
-Section: python
-Priority: optional
-Build-Depends: debhelper (>= 10),
-               dh-python,
-               python-all-dev,
-               python-nose
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/python-qcli/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/python-qcli/trunk/
-Homepage: https://pypi.python.org/pypi/qcli/0.1.0
-X-Python-Version: >= 2.6
-
-Package: python-qcli
-Architecture: any
-Depends: ${shlibs:Depends},
-         ${python:Depends},
-         ${misc:Depends}
-Description: separated module of pyqi needed for QIIME package
- The qiime package needs this as new dependency which is not part of the
- main pyqi package.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 1714ff6..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,45 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Contact: Greg Caporaso <gregcaporaso at gmail.com>
-Source: https://pypi.python.org/pypi/qcli
-
-Files: *
-Copyright: 2013-2014 Greg Caporaso <gregcaporaso at gmail.com>
-License: BSDlike
-Comment: Upstream has confirmed the license online
- Since there is no explicit copy of the licensing conditions upstream
- was asked for explicite permission to distribute.  This was given on
- the Debian Med mailing list here:
- .
-   https://lists.alioth.debian.org/pipermail/debian-med-packaging/2013-December/024166.html
- .
- The package is only temporary needed for QIIME 1.8 and in the next
- major upstream release of QIIME it will be replaced by pyqi which
- has an explicite licensing statement.
-
-Files: debian/*
-Copyright: 2013-2016 Andreas Tille <tille at debian.org>
-License: BSDlike
-
-License: BSDlike
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions are met:
-        * Redistributions of source code must retain the above copyright
-          notice, this list of conditions and the following disclaimer.
-        * Redistributions in binary form must reproduce the above copyright
-          notice, this list of conditions and the following disclaimer in the
-          documentation and/or other materials provided with the distribution.
-        * Neither the name BiPy nor the names of its contributors may be used to
-          endorse or promote products derived from this software without specific
-          prior written permission.
- .
-    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-    DISCLAIMED. IN NO EVENT SHALL THE BIPY DEVELOPMENT TEAM BE LIABLE FOR ANY
-    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index ae40d9d..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/make -f
-
-DH_VERBOSE := 1
-
-pkg      := $(shell dpkg-parsechangelog | sed -n 's/^Source: //p')
-bindir   := $(CURDIR)/debian/$(pkg)/usr/bin
-examples := $(CURDIR)/debian/$(pkg)/usr/share/doc/$(pkg)/examples
-
-pybuilddir := $(shell dpkg-architecture -qDEB_BUILD_ARCH_OS)-$(shell dpkg-architecture -qDEB_BUILD_GNU_CPU)
-
-export PYBUILD_NAME=qcli
-
-
-%:
-	dh $@ --with python2 --buildsystem=pybuild
-
-# we only need this package as qiime dependency - there is no point in bloating /usr/bin with these scripts
-override_dh_installexamples:
-	mv $(bindir) $(examples)
-
-override_dh_auto_test:
-ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
-	set -e -x;\
-	for pyv in `pyversions -dv` ; do \
-	    cd build/; \
-	    ln -s ../python-code/tests; \
-	    env PYTHONPATH=lib.$(pybuilddir)-$${pyv} nosetests ; \
-	done
-endif
-
-get-orig-source:
-	mkdir -p ../tarballs
-	uscan --verbose --force-download --destdir=../tarballs
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index b3a10e8..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,2 +0,0 @@
-version=4
-http://pypi.debian.net/qcli/qcli-(.+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
diff --git a/qcli/__init__.py b/qcli/__init__.py
new file mode 100644
index 0000000..57a605d
--- /dev/null
+++ b/qcli/__init__.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+__author__ = "The BiPy Development Team"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Rob Knight", 
+               "Greg Caporaso", 
+               "Gavin Huttley", 
+               "Daniel McDonald",
+               "Jai Ram Rideout"] 
+__license__ = "GPL"
+__version__ = "0.1.1"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+# import most commonly used objects and functions so they can
+# be imported directly from qlci (e.g., from qcli import make_option)
+from qcli.option_parsing import (
+ make_option, 
+ parse_command_line_parameters)
+from qcli.test import (run_script_usage_tests)
+from qcli.util import (qcli_system_call)
+
+__all__ = ['option_parsing','test','util']
diff --git a/qcli/option_parsing.py b/qcli/option_parsing.py
new file mode 100644
index 0000000..5eb537b
--- /dev/null
+++ b/qcli/option_parsing.py
@@ -0,0 +1,350 @@
+#!/usr/bin/env python
+""" Utilities for parsing command line options and arguments
+
+This code was derived from PyCogent (www.pycogent.org) and QIIME
+(www.qiime.org), where it was initally developed. It has been ported 
+to qcli to support accessing this functionality without those 
+dependencies.
+
+"""
+
+from copy import copy
+import types
+import sys
+from optparse import (OptionParser, OptionGroup, Option, 
+                      OptionValueError, OptionError)
+from os import popen, remove, makedirs, getenv
+from os.path import join, abspath, exists, isdir, isfile, split
+from glob import glob
+
+
+__author__ = "Greg Caporaso, Gavin Huttley, Rob Knight, Daniel McDonald"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Greg Caporaso",
+               "Daniel McDonald",
+               "Gavin Huttley",
+               "Rob Knight",
+               "Jose Antonio Navas Molina"]
+__license__ = "GPL"
+__version__ = "0.1.1"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+
+class QOptionParser(OptionParser):
+    """QCLI's OptionParser subclass"""
+    def __init__(self, error_suffix='', **kwargs):
+        OptionParser.__init__(self, **kwargs)
+        # error suffix specifies a message that will be appended to every
+        # error shown with the error method
+        self.error_suffix = error_suffix
+
+    def error(self, msg):
+        # based on the built-in optparse.py error method
+        self.exit(2, "Error in %s: %s\n%s" % (self.get_prog_name(), msg,
+                                            self.error_suffix))
+
+## Definition of CogentOption option type, a subclass of Option that
+## contains specific types for filepaths and directory paths. This 
+## will be particularly useful for graphical interfaces that make 
+## use of the script_info dictionary as they can then distinguish 
+## paths from ordinary strings
+def check_existing_filepath(option, opt, value):
+    if not exists(value):
+        raise OptionValueError(
+            "option %s: file does not exist: %r" % (opt, value))
+    elif not isfile(value):
+        raise OptionValueError(
+            "option %s: not a regular file (can't be a directory!): %r" % (opt, value))
+    else:
+        return value
+
+def check_existing_filepaths(option, opt, value):
+    paths = []
+    for v in value.split(','):
+        fps = glob(v)
+        if len(fps) == 0:            
+            raise OptionValueError(
+             "No filepaths match pattern/name '%s'. "
+             "All patterns must be matched at least once." % v)
+        else:
+            paths.extend(fps)
+    values = []
+    for v in paths:
+        check_existing_filepath(option,opt,v)
+        values.append(v)
+    return values
+
+def check_existing_dirpath(option, opt, value):
+    if not exists(value):
+        raise OptionValueError(
+            "option %s: directory does not exist: %r" % (opt, value))
+    elif not isdir(value):
+        raise OptionValueError(
+            "option %s: not a directory (can't be a file!): %r" % (opt, value))
+    else:
+        return value
+
+def check_new_filepath(option, opt, value):
+    return value
+        
+def check_new_dirpath(option, opt, value):
+    return value
+    
+def check_existing_path(option, opt, value):
+    if not exists(value):
+        raise OptionValueError(
+            "option %s: path does not exist: %r" % (opt, value))
+    return value
+    
+def check_new_path(option, opt, value):
+    return value
+
+def check_multiple_choice(option, opt, value):
+    #split_char = ';' if ';' in value else ','
+    values = value.split(option.split_char)
+    for v in values:
+        if v not in option.mchoices:
+            choices = ",".join(map(repr, option.mchoices))
+            raise OptionValueError(
+                "option %s: invalid choice: %r (choose from %s)"
+                % (opt, v, choices))
+    return values
+
+def check_blast_db(option, opt, value):
+    db_dir, db_name = split(abspath(value))
+    if not exists(db_dir):
+        raise OptionValueError(
+            "option %s: path does not exists: %r" % (opt, db_dir))
+    elif not isdir(db_dir):
+        raise OptionValueError(
+            "option %s: not a directory: %r" % (opt, db_dir))
+    return value
+
+class QcliOption(Option):
+    ATTRS = Option.ATTRS + ['mchoices','split_char']
+
+    TYPES = Option.TYPES + ("existing_path",
+                            "new_path",
+                            "existing_filepath",
+                            "existing_filepaths",
+                            "new_filepath",
+                            "existing_dirpath",
+                            "new_dirpath",
+                            "multiple_choice",
+                            "blast_db")
+    TYPE_CHECKER = copy(Option.TYPE_CHECKER)
+    # for cases where the user specifies an existing file or directory
+    # as input, but it can be either a dir or a file
+    TYPE_CHECKER["existing_path"] = check_existing_path
+    # for cases where the user specifies a new file or directory
+    # as output, but it can be either a dir or a file
+    TYPE_CHECKER["new_path"] = check_new_path
+    # for cases where the user passes a single existing file
+    TYPE_CHECKER["existing_filepath"] = check_existing_filepath
+    # for cases where the user passes one or more existing files
+    # as a comma-separated list - paths are returned as a list
+    TYPE_CHECKER["existing_filepaths"] = check_existing_filepaths
+    # for cases where the user is passing a new path to be 
+    # create (e.g., an output file)
+    TYPE_CHECKER["new_filepath"] = check_new_filepath
+    # for cases where the user is passing an existing directory
+    # (e.g., containing a set of input files)
+    TYPE_CHECKER["existing_dirpath"] = check_existing_dirpath
+    # for cases where the user is passing a new directory to be 
+    # create (e.g., an output dir which will contain many result files)
+    TYPE_CHECKER["new_dirpath"] = check_new_dirpath
+    # for cases where the user is passing one or more values
+    # as comma- or semicolon-separated list
+    # choices are returned as a list
+    TYPE_CHECKER["multiple_choice"] = check_multiple_choice
+    # for cases where the user is passing a blast database option
+    # blast_db is returned as a string
+    TYPE_CHECKER["blast_db"] = check_blast_db
+
+    def _check_multiple_choice(self):
+        if self.type == "multiple_choice":
+            if self.mchoices is None:
+                raise OptionError(
+                    "must supply a list of mchoices for type '%s'" % self.type, self)
+            elif type(self.mchoices) not in (types.TupleType, types.ListType):
+                raise OptionError(
+                    "choices must be a list of strings ('%s' supplied)"
+                    % str(type(self.mchoices)).split("'")[1], self)
+            if self.split_char is None:
+                self.split_char = ','
+        elif self.mchoices is not None:
+            raise OptionError(
+                "must not supply mchoices for type %r" % self.type, self)
+
+    CHECK_METHODS = Option.CHECK_METHODS + [_check_multiple_choice]
+
+# When this code was in PyCogent, the option object was called
+# CogentOption, so leaving that name in place for backward compatibility.
+make_option = CogentOption = QcliOption
+
+## End definition of new option type
+
+def build_usage_lines(required_options,
+    script_description,
+    script_usage,
+    optional_input_line,
+    required_input_line):
+    """ Build the usage string from components 
+    """
+    line1 = 'usage: %prog [options] ' + '{%s}' %\
+     ' '.join(['%s %s' % (str(ro),ro.dest.upper())\
+               for ro in required_options])
+    usage_examples = []
+    for title, description, command in script_usage:
+        title = title.strip(':').strip()
+        description = description.strip(':').strip()
+        command = command.strip()
+        if title:
+            usage_examples.append('%s: %s\n %s' %\
+             (title,description,command))
+        else:
+            usage_examples.append('%s\n %s' % (description,command))
+    usage_examples = '\n\n'.join(usage_examples)
+    lines = (line1,
+             '', # Blank line
+             optional_input_line,
+             required_input_line,
+             '', # Blank line
+             script_description,
+             '', # Blank line
+             'Example usage: ',\
+             'Print help message and exit',
+             ' %prog -h\n',
+             usage_examples)
+    return '\n'.join(lines)
+
+def set_parameter(key,kwargs,default=None):
+    try:
+        return kwargs[key]
+    except KeyError:
+        return default
+        
+def set_required_parameter(key,kwargs):
+    try:
+        return kwargs[key]
+    except KeyError:
+        raise KeyError,\
+         "parse_command_line_parameters requires value for %s" % key
+        
+def parse_command_line_parameters(**kwargs):
+    """ Constructs the OptionParser object and parses command line arguments
+    
+        parse_command_line_parameters takes a dict of objects via kwargs which
+         it uses to build command line interfaces according to standards 
+         developed in the Knight Lab, and enforced in QIIME. The currently 
+         supported options are listed below with their default values. If no 
+         default is provided, the option is required.
+        
+        script_description
+        script_usage = [("","","")]
+        version
+        required_options=None
+        optional_options=None
+        suppress_verbose=False
+        disallow_positional_arguments=True
+        help_on_no_arguments=True
+        optional_input_line = '[] indicates optional input (order unimportant)'
+        required_input_line = '{} indicates required input (order unimportant)'
+        
+       These values can either be passed directly, as:
+        parse_command_line_parameters(script_description="My script",\
+                                     script_usage=[('Print help','%prog -h','')],\
+                                     version=1.0)
+                                     
+       or they can be passed via a pre-constructed dict, as:
+        d = {'script_description':"My script",\
+             'script_usage':[('Print help','%prog -h','')],\
+             'version':1.0}
+        parse_command_line_parameters(**d)
+    
+    """
+    # Get the options, or defaults if none were provided.
+    script_description = set_required_parameter('script_description',kwargs)
+    version = set_required_parameter('version',kwargs)
+    script_usage = set_parameter('script_usage',kwargs,[("","","")])
+    required_options = set_parameter('required_options',kwargs,[])
+    optional_options = set_parameter('optional_options',kwargs,[])
+    suppress_verbose = set_parameter('suppress_verbose',kwargs,False)
+    disallow_positional_arguments =\
+     set_parameter('disallow_positional_arguments',kwargs,True)
+    help_on_no_arguments = set_parameter('help_on_no_arguments',kwargs,True)
+    optional_input_line = set_parameter('optional_input_line',kwargs,\
+        '[] indicates optional input (order unimportant)')
+    required_input_line = set_parameter('required_input_line',kwargs,\
+        '{} indicates required input (order unimportant)')
+    # command_line_text will usually be nothing, but can be passed for
+    # testing purposes
+    command_line_args = set_parameter('command_line_args',kwargs,None)
+    
+    # Build the usage and version strings
+    usage = build_usage_lines(required_options,script_description,script_usage,\
+                              optional_input_line,required_input_line)
+    version = 'Version: %prog ' + version
+    
+    # Instantiate the command line parser object
+    parser = QOptionParser(error_suffix=kwargs.pop('error_suffix', ''),
+                           usage=usage, version=version)
+    parser.exit = set_parameter('exit_func',kwargs,parser.exit)
+    
+    # If no arguments were provided, print the help string (unless the
+    # caller specified not to)
+    if help_on_no_arguments and (not command_line_args) and len(sys.argv) == 1:
+        parser.print_usage()
+        return parser.exit(-1)
+
+    
+    # Process the required options
+    if required_options:
+        # Define an option group so all required options are
+        # grouped together, and under a common header
+        required = OptionGroup(parser, "REQUIRED options",
+         "The following options must be provided under all circumstances.")
+        for ro in required_options:
+            # if the option doesn't already end with [REQUIRED], 
+            # add it.
+            if not ro.help.strip().endswith('[REQUIRED]'):
+                ro.help += ' [REQUIRED]'
+            required.add_option(ro)
+        parser.add_option_group(required)
+
+    # Add a verbose parameter (if the caller didn't specify not to)
+    if not suppress_verbose:
+        parser.add_option('-v','--verbose',action='store_true',\
+           dest='verbose',help='Print information during execution '+\
+           '-- useful for debugging [default: %default]',default=False)
+
+    # Add the optional options
+    map(parser.add_option,optional_options)
+    
+    # Parse the command line
+    # command_line_text will None except in test cases, in which 
+    # case sys.argv[1:] will be parsed
+    opts,args = parser.parse_args(command_line_args)
+    
+    # If positional arguments are not allowed, and any were provided,
+    # raise an error.
+    if disallow_positional_arguments and len(args) != 0:
+        parser.error("Positional argument detected: %s\n" % str(args[0]) +\
+         " Be sure all parameters are identified by their option name.\n" +\
+         " (e.g.: include the '-i' in '-i INPUT_DIR')")
+
+    # Test that all required options were provided.
+    if required_options:
+        required_option_ids = [o.dest for o in required.option_list]
+        for required_option_id in required_option_ids:
+            if getattr(opts,required_option_id) == None:
+                return parser.error('Required option --%s omitted.' \
+                             % required_option_id)
+            
+    # Return the parser, the options, and the arguments. The parser is returned
+    # so users have access to any additional functionality they may want at 
+    # this stage -- most commonly, it will be used for doing custom tests of 
+    # parameter values.
+    return parser, opts, args
+
diff --git a/qcli/test.py b/qcli/test.py
new file mode 100644
index 0000000..36675b4
--- /dev/null
+++ b/qcli/test.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+""" Utilities for parsing command line options and arguments
+
+This code was derived from QIIME (www.qiime.org), where it was initally
+developed. It has been ported to qcli to support accessing this functionality 
+without those dependencies.
+
+"""
+
+import signal
+from os.path import isdir, split, join, abspath, exists
+from os import chdir, getcwd
+from shutil import copytree, rmtree
+from glob import glob
+from site import addsitedir
+from qcli.util import (qcli_system_call, 
+                       remove_files)
+
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Greg Caporaso"]
+__license__ = "GPL"
+__version__ = "0.1.1"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+### Code for timing out tests that exceed a time limit
+## The test case timing code included in this file is adapted from
+## recipes provided at:
+##  http://code.activestate.com/recipes/534115-function-timeout/
+##  http://stackoverflow.com/questions/492519/timeout-on-a-python-function-call
+
+# to use this, call initiate_timeout(allowed_seconds_per_test) in 
+# TestCase.setUp() and then disable_timeout() in TestCase.tearDown()
+
+class TimeExceededError(Exception):
+    pass
+
+def initiate_timeout(seconds=60):
+    
+    def timeout(signum, frame):
+        raise TimeExceededError,\
+         "Test failed to run in allowed time (%d seconds)." % seconds
+    
+    signal.signal(signal.SIGALRM, timeout)
+    # set the 'alarm' to go off in seconds seconds
+    signal.alarm(seconds)
+
+def disable_timeout():
+    # turn off the alarm
+    signal.alarm(0)
+
+### End code for timing out tests that exceed a time limit
+
+def run_script_usage_tests(test_data_dir,
+                           scripts_dir,
+                           working_dir,
+                           verbose=False,
+                           tests=None,
+                           failure_log_fp=None,
+                           force_overwrite=False,
+                           timeout=60):
+    """ Test script_usage examples when test data is present in test_data_dir
+
+        Returns a result summary string and the number of script usage
+        examples (i.e. commands) that failed.
+    """
+    # process input filepaths and directories
+    test_data_dir = abspath(test_data_dir)
+    working_dir = join(working_dir,'script_usage_tests')
+    if force_overwrite and exists(working_dir):
+        rmtree(working_dir)
+    if failure_log_fp != None:
+        failure_log_fp = abspath(failure_log_fp)
+
+    if tests == None:
+        tests = [split(d)[1] for d in sorted(glob('%s/*' % test_data_dir)) if isdir(d)]
+    
+    if verbose:
+        print 'Tests to run:\n %s' % ' '.join(tests)
+    
+    addsitedir(scripts_dir)
+    
+    failed_tests = []
+    warnings = []
+    total_tests = 0
+    for test in tests:
+        
+        # import the usage examples - this is possible because we added 
+        # scripts_dir to the PYTHONPATH above
+        script_fn = '%s/%s.py' % (scripts_dir,test)
+        script = __import__(test)
+        usage_examples = script.script_info['script_usage']
+        
+        if verbose:
+            print 'Testing %d usage examples from: %s' % (len(usage_examples),script_fn)
+        
+        # init the test environment
+        test_input_dir = '%s/%s' % (test_data_dir,test)
+        test_working_dir = '%s/%s' % (working_dir,test)
+        copytree(test_input_dir,test_working_dir)
+        chdir(test_working_dir)
+        
+        # remove pre-exisitng output files if any
+        try:
+            script_usage_output_to_remove = script.script_info['script_usage_output_to_remove']
+        except KeyError:
+            script_usage_output_to_remove = []
+        for e in script_usage_output_to_remove:
+            rmtree(e.replace('$PWD',getcwd()),ignore_errors=True)
+            remove_files([e.replace('$PWD',getcwd())],error_on_missing=False)
+        
+        if verbose:
+            print ' Running tests in: %s' % getcwd()
+            print ' Tests:'
+        
+        for usage_example in usage_examples:
+            if '%prog' not in usage_example[2]:
+                warnings.append('%s usage examples do not all use %%prog to represent the command name. You may not be running the version of the command that you think you are!' % test)
+            cmd = usage_example[2].replace('%prog',script_fn)
+            if verbose:
+                print '  %s' % cmd,
+            
+            timed_out = False
+            initiate_timeout(timeout)
+            try:
+                stdout, stderr, return_value = qcli_system_call(cmd)
+            except TimeExceededError:
+                timed_out = True
+            else:
+                disable_timeout()
+            
+            total_tests += 1
+            if timed_out:
+                # Add a string instead of return_value - if fail_tests ever ends
+                # up being returned from this function we'll want to code this as 
+                # an int for consistency in the return value type.
+                failed_tests.append((cmd, "", "", "None, time exceeded"))
+                if verbose: print ": Timed out"
+            elif return_value != 0:
+                failed_tests.append((cmd, stdout, stderr, return_value))
+                if verbose: print ": Failed"
+            else:
+                pass
+                if verbose: print ": Pass"
+        
+        if verbose:
+            print ''
+            
+    if failure_log_fp:
+        failure_log_f = open(failure_log_fp,'w')
+        if len(failed_tests) == 0:
+            failure_log_f.write('All script interface tests passed.\n')
+        else:
+            i = 1
+            for cmd, stdout, stderr, return_value in failed_tests:
+                failure_log_f.write('**Failed test %d:\n%s\n\nReturn value: %s\n\nStdout:\n%s\n\nStderr:\n%s\n\n' % (i,cmd,str(return_value), stdout, stderr))
+                i += 1
+        failure_log_f.close()
+    
+    
+    if warnings:
+        print 'Warnings:'
+        for warning in warnings:
+            print ' ' + warning
+        print ''
+    
+    result_summary = 'Ran %d commands to test %d scripts. %d of these commands failed.' % (total_tests,len(tests),len(failed_tests))
+    if len(failed_tests) > 0:
+        failed_scripts = set([split(e[0].split()[0])[1] for e in failed_tests])
+        result_summary += '\nFailed scripts were: %s' % " ".join(failed_scripts)
+    if failure_log_fp:
+        result_summary += "\nFailures are summarized in %s" % failure_log_fp
+    
+    rmtree(working_dir)
+    
+    return result_summary, len(failed_tests)
diff --git a/qcli/util.py b/qcli/util.py
new file mode 100644
index 0000000..24dd3d9
--- /dev/null
+++ b/qcli/util.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+""" Utilities for parsing command line options and arguments
+
+This code was derived from QIIME (www.qiime.org), where it was initally
+developed. It has been ported to qcli to support accessing this functionality 
+without those dependencies.
+
+"""
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Greg Caporaso"]
+__license__ = "GPL"
+__version__ = "0.1.1"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+from os import remove
+from subprocess import Popen, PIPE, STDOUT
+
+def qcli_system_call(cmd, shell=True):
+    """Call cmd and return (stdout, stderr, return_value).
+
+    cmd can be either a string containing the command to be run, or a sequence
+    of strings that are the tokens of the command.
+
+    Please see Python's subprocess. Popen for a description of the shell
+    parameter and how cmd is interpreted differently based on its value.
+    
+    This function is ported from QIIME (previously qiime_system_call).
+    """
+    proc = Popen(cmd,
+                 shell=shell,
+                 universal_newlines=True,
+                 stdout=PIPE,
+                 stderr=PIPE)
+    # communicate pulls all stdout/stderr from the PIPEs to 
+    # avoid blocking -- don't remove this line!
+    stdout, stderr = proc.communicate()
+    return_value = proc.returncode
+    return stdout, stderr, return_value
+
+def remove_files(list_of_filepaths, error_on_missing=True):
+    """Remove list of filepaths, optionally raising an error if any are missing
+    
+       This function is ported from PyCogent.
+    """
+    missing = []
+    for fp in list_of_filepaths:
+        try:
+            remove(fp)
+        except OSError:
+            missing.append(fp)
+
+    if error_on_missing and missing:
+        raise OSError, "Some filepaths were not accessible: %s" % '\t'.join(missing)
+        
\ No newline at end of file
diff --git a/scripts/qcli_make_rst b/scripts/qcli_make_rst
new file mode 100755
index 0000000..25d53e8
--- /dev/null
+++ b/scripts/qcli_make_rst
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+# File created on 15 Feb 2010
+from __future__ import division
+
+__author__ = "Jesse Stombaugh"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Jesse Stombaugh",
+               "Greg Caporaso"]
+__license__ = "GPL"
+__version__ = "0.1.0"
+__maintainer__ = "Jesse Stombaugh"
+__email__ = "gregcaporaso at gmail.com"
+
+import os
+from os import makedirs
+from os.path import exists, abspath, split, splitext
+from string import replace
+import types
+import re
+from sys import exit, stderr
+from site import addsitedir
+from qcli import (parse_command_line_parameters, 
+                  make_option)
+
+rst_text= \
+'''\
+.. _%s:
+
+.. index:: %s.py
+
+*%s.py* -- %s
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Description:**
+
+%s
+
+
+**Usage:** :file:`%s.py [options]`
+
+**Input Arguments:**
+
+.. note::
+
+%s
+
+**Output:**
+
+%s
+
+%s
+
+'''
+
+script_info={}
+script_info['brief_description']="""Make Sphinx RST file for one or more qcli-based scripts"""
+script_info['script_description'] = """This script will take a qcli script and convert the usage strings and options to generate a documentation .rst file."""
+script_info['script_usage']=[]
+script_info['script_usage'].append(("""Create RST for many files""",
+                                    """Create rst files for all files ending with .py in the scripts/ directory. Write the rst files to the rst directory. Note that if the value you pass for -i contains a wildcard character (e.g., "*"), the value must be wrapped in quotes.""",
+                                    """%prog -i "scripts/*py" -o rst"""))
+script_info['output_description']="""This script will output one or more Sphinx rst-formatted files."""
+
+script_info['required_options'] = [
+ make_option('-i','--input_fps',type='existing_filepaths',
+             help='the input file(s) to generate rst files for'),
+ make_option('-o','--output_dir',type='new_filepath',
+             help='the directory where the resulting rst file(s) should be written'),
+]
+
+script_info['version'] = __version__
+
+
+def convert_py_file_to_link(input_str):
+    m=re.compile('[\w]+\.py')
+    python_script_names=set(m.findall(input_str))
+    
+    if python_script_names:
+        script_w_link=input_str
+        for i in python_script_names:
+            individual_script_name=os.path.splitext(i)
+            script_w_link=script_w_link.replace(i, '`'+ i + ' <./' + \
+                           individual_script_name[0] + '.html>`_')
+        
+        return script_w_link
+    else:
+        return input_str
+
+def main():
+    option_parser, opts, args = parse_command_line_parameters(**script_info)
+
+    #Create a list of the scripts to create rst files for.
+    file_names = opts.input_fps
+    for fn in file_names:
+        addsitedir(abspath(split(fn)[0]))
+    
+    #Identify the directory where results should be written.
+    output_dir = opts.output_dir
+    if output_dir and not output_dir.endswith('/'):
+        output_dir = output_dir + '/'
+    if not exists(output_dir):
+        makedirs(output_dir)
+
+    script={}
+    #Iterate through list of filenames
+    for filename in file_names:
+        #Get only the name of the script and remove other path information.
+        filename = splitext(split(filename)[1])[0]
+
+        #Import the script file to get the dictionary values
+        try:
+            script=__import__(filename)
+        except ImportError:
+            raise ImportError, "Can't import %s" % filename
+        
+        #Define output file path
+        outf=os.path.join(output_dir,'%s.rst' % filename)
+        
+        #This try block attempts to parse the dictionary and if the dictionary
+        #is not present, then it will write that information to stdout
+        try:
+
+            imported_brief_description=script.script_info['brief_description']
+            imported_script_description=script.script_info['script_description']
+
+            new_script_description = \
+                    convert_py_file_to_link(imported_script_description)
+            #print new_script_description
+            inputs=''
+            if script.script_info.has_key('required_options') and \
+             script.script_info['required_options']<>[]:
+                inputs= '\t\n\t**[REQUIRED]**\n\t\t\n'
+                for i in script.script_info['required_options']:
+                    # when no default is provided in the call to make_option,
+                    # the value of i.default is a tuple -- this try/except
+                    # handles the diff types that i.default can be
+                    try:
+                        if i.default<>'':
+                            if i.default[0] == 'NO':
+                                # i.default is a tuple, so defualt hasn't been
+                                # set by the user, and it should therefore be 
+                                # None
+                                defaults = None
+                            else:
+                                # i.default is a string
+                                defaults = i.default
+                        else:
+                            defaults=None
+                    except TypeError:
+                        # i.default is not a string or a tuple (e.g., it's an 
+                        # int or None)
+                        defaults = i.default
+            
+                    p=re.compile('\%default')
+                    help_str=p.sub(str(defaults),i.help)
+                    new_help_str=convert_py_file_to_link(help_str)
+                    new_help_str=new_help_str[0].upper() + new_help_str[1:]
+        
+                    cmd_arg=str(i).replace('--','`-`-').replace('/',', ')
+                    inputs=inputs+'\t'+str(cmd_arg)+'\n\t\t'+ new_help_str+'\n'
+                    
+                    
+            if script.script_info.has_key('optional_options') and  \
+             script.script_info['optional_options']<>[]:
+                inputs=inputs + '\t\n\t**[OPTIONAL]**\n\t\t\n'
+                for i in script.script_info['optional_options']:
+                    # when no default is provided in the call to make_option,
+                    # the value of i.default is a tuple -- this try/except
+                    # handles the diff types that i.default can be
+                    try:
+                        if i.default<>'':
+                            if i.default[0] == 'NO':
+                                # i.default is a tuple, so defualt hasn't been
+                                # set by the user, and it should therefore be 
+                                # None
+                                defaults = None
+                            else:
+                                # i.default is a string
+                                defaults = i.default
+                        else:
+                            defaults=i.default
+                    except TypeError:
+                        # i.default is not a string or a tuple (e.g., it's an 
+                        # int or None)
+                        defaults = i.default
+            
+                    p=re.compile('\%default')
+                    help_str=p.sub(str(defaults),i.help)
+                    new_help_str=convert_py_file_to_link(help_str)
+                    new_help_str=new_help_str[0].upper() + new_help_str[1:]
+                    
+                    cmd_arg=str(i).replace('--','`-`-').replace('/',', ')
+                    inputs=inputs+'\t'+str(cmd_arg)+'\n\t\t'+ new_help_str+'\n'
+
+            if (not script.script_info.has_key('required_options') and not script.script_info.has_key('optional_options')) or \
+                (script.script_info['required_options']==[] and script.script_info['optional_options']==[]):
+                inputs='\t\n\tNone'
+                
+            script_examples=''
+            for ex in script.script_info['script_usage']:
+                example_title=ex[0].strip()
+                if example_title <> '':
+                    if example_title.endswith(':'):
+                        script_examples += '\n**' + ex[0] + '**\n'
+                    else:
+                        script_examples += '\n**' + ex[0] + ':**\n'
+                if ex[1] <> '':
+                    script_ex=convert_py_file_to_link(ex[1])
+                    script_examples += '\n' + script_ex + '\n'
+                if ex[2] <>'':
+                    new_cmd=ex[2].replace('%prog',filename+'.py')
+                    script_examples += '\n::\n\n\t' + new_cmd + '\n'
+    
+            script_out = \
+                  script.script_info['output_description'].replace('--','`-`-')
+            new_script_out=convert_py_file_to_link(script_out)
+
+            output_text = rst_text % (filename,
+                                      filename,
+                                      filename,
+                                      imported_brief_description,
+                                      new_script_description,
+                                      filename,
+                                      inputs,
+                                      new_script_out,
+                                      script_examples)
+
+            ###Write rst file
+            f = open(outf, 'w')
+            f.write((output_text.replace('%prog',filename+'.py')))
+            f.close()
+            
+            #script.close()
+        except AttributeError:
+            print "%s: This file does not contain the appropriate dictionary" \
+            % (filename)
+        except KeyError:
+            print "%s: This file does not contain necessary fields" \
+            % (filename)
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/scripts/qcli_make_script b/scripts/qcli_make_script
new file mode 100755
index 0000000..1c68c03
--- /dev/null
+++ b/scripts/qcli_make_script
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# make_qiime_py_file.py
+
+"""
+This is a script which will add headers and footers to new qcli scripts
+and make them executable. 
+"""
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Greg Caporaso"]
+__license__ = "GPL"
+__version__ = "0.1.0"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+from sys import exit
+from os import popen
+from os.path import exists
+from time import strftime
+from optparse import OptionParser
+from qcli import (parse_command_line_parameters, 
+                        make_option)
+
+script_info={}
+script_info['brief_description']="""Create a template qcli script.""" 
+script_info['script_description']="""This script will create a template qcli script and make it executable.""" 
+script_info['script_usage']=[] 
+script_info['script_usage'].append(("""Example usage""","""Create a new script""","""%prog -a "Greg Caporaso" -e gregcaporaso at gmail.com -o my_script.py"""))  
+script_info['script_usage_output_to_remove'] = ['my_script.py']
+script_info['output_description']="""The result of this script is a qcli template script."""
+
+script_info['required_options']=[\
+    make_option('-o','--output_fp',help="The output filepath.")
+] 
+
+script_info['optional_options']=[
+    make_option('-a','--author_name',
+     help="The script author's (probably you) name to be included in"+\
+     " the header variables. This will typically need to be enclosed "+\
+     " in quotes to handle spaces. [default:%default]",default='AUTHOR_NAME'),
+    make_option('-e','--author_email',
+     help="The script author's (probably you) e-mail address to be included in"+\
+     " the header variables. [default:%default]",default='AUTHOR_EMAIL'),
+    make_option('-c','--copyright',
+     help="The copyright information to be included in"+\
+     " the header variables. [default:%default]",default='Copyright 2013, The BiPy project')
+] 
+
+script_info['version'] = __version__
+
+def main():
+    option_parser, opts, args = parse_command_line_parameters(**script_info)
+
+
+    header_block = """#!/usr/bin/env python
+# File created on %s
+from __future__ import division
+
+__author__ = "AUTHOR_NAME"
+__copyright__ = "COPYRIGHT"
+__credits__ = ["AUTHOR_NAME"]
+__license__ = "GPL"
+__version__ = "0.1.0"
+__maintainer__ = "AUTHOR_NAME"
+__email__ = "AUTHOR_EMAIL"
+""" % strftime('%d %b %Y')
+
+    script_block = """
+from qcli import (parse_command_line_parameters, 
+                  make_option)
+
+script_info = {}
+script_info['brief_description'] = ""
+script_info['script_description'] = ""
+script_info['script_usage'] = []
+script_info['script_usage'].append(("","",""))
+script_info['output_description']= ""
+script_info['required_options'] = [
+ # Example required option
+ #make_option('-i','--input_fp',type="existing_filepath",help='the input filepath'),
+]
+script_info['optional_options'] = [
+ # Example optional option
+ #make_option('-o','--output_dir',type="new_dirpath",help='the output directory [default: %default]'),
+]
+script_info['version'] = __version__"""
+
+    output_fp = opts.output_fp
+
+    # Check to see if the file which was requested to be created
+    # already exists -- if it does, print a message and exit
+    if exists(output_fp):
+        print '\n'.join(["The file name you requested already exists.",\
+            " Delete extant file and rerun script if it should be overwritten.",\
+            " Otherwise change the file name (-o).",\
+            "Creating no files and exiting..."])
+        exit(1) 
+
+    # Create the header data
+    header_block = header_block.replace('AUTHOR_NAME',opts.author_name)
+    header_block = header_block.replace('AUTHOR_EMAIL',opts.author_email)
+    header_block = header_block.replace('COPYRIGHT',opts.copyright)
+    lines = [header_block]
+
+    lines.append(script_block)
+    lines += ['','','','def main():',\
+     '    option_parser, opts, args =\\',\
+     '       parse_command_line_parameters(**script_info)',\
+     '','',\
+     'if __name__ == "__main__":',\
+     '    main()']
+
+
+    # Open the new file for writing and write it.
+    f = open(output_fp,'w')
+    f.write('\n'.join(lines))
+    f.close()
+    
+    # change mode to 755
+    chmod_string = ' '.join(['chmod 755',output_fp])
+    popen(chmod_string)
+        
+
+
+
+
+if __name__ == "__main__":
+    main()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..2f0fd4e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+__author__ = "The BiPy Development Team"
+__copyright__ = "Copyright 2013, The BiPy Project"
+__credits__ = ["Rob Knight", "Greg Caporaso", ] 
+__license__ = "GPL"
+__version__ = "0.1.1"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+
+from distutils.core import setup
+from glob import glob
+
+setup(name='qcli',
+      version='0.1.1',
+      packages=['qcli'],
+      scripts=glob('scripts/qcli*')
+      )
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-qcli.git



More information about the debian-med-commit mailing list