[med-svn] [python-burrito] 11/15: New upstream version 0.9.1
Andreas Tille
tille at debian.org
Wed Nov 29 09:43:15 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository python-burrito.
commit ac7c03881420b3496f1a7a9abaa798f53762eb6f
Author: Andreas Tille <tille at debian.org>
Date: Wed Nov 29 10:38:46 2017 +0100
New upstream version 0.9.1
---
CHANGELOG.md | 9 +
LICENSE | 27 +
MANIFEST.in | 8 +
PKG-INFO | 69 ++
README.rst | 44 ++
burrito.egg-info/PKG-INFO | 69 ++
burrito.egg-info/SOURCES.txt | 16 +
burrito.egg-info/dependency_links.txt | 1 +
burrito.egg-info/requires.txt | 6 +
burrito.egg-info/top_level.txt | 1 +
burrito/__init__.py | 9 +
burrito/parameters.py | 492 ++++++++++++
burrito/tests/__init__.py | 7 +
burrito/tests/test_parameters.py | 659 +++++++++++++++++
burrito/tests/test_util.py | 1313 +++++++++++++++++++++++++++++++++
burrito/util.py | 791 ++++++++++++++++++++
debian/changelog | 18 -
debian/compat | 1 -
debian/control | 54 --
debian/copyright | 40 -
debian/patches/better_error_reporting | 24 -
debian/patches/series | 1 -
debian/rules | 11 -
debian/source/format | 1 -
debian/watch | 2 -
setup.cfg | 5 +
setup.py | 55 ++
27 files changed, 3581 insertions(+), 152 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ff702c9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# burrito changelog
+
+## Version 0.9.1 (2015-05-22)
+
+* Updated default temporary directory from ``/tmp`` to python's ``tempfile.gettempdir()``. This should address many of the issues with temporary files being written to ``/tmp``, which sometimes doesn't exist, doesn't provide a lot of storage, or is not shared across cluster nodes. It is still possible that individual burrito fillings (i.e., ``CommandLineApplication`` derived classes) can hard code ``/tmp``, so care should be taken when writing those derived classes to avoid that.
+
+## Version 0.9.0 (2014-08-04)
+
+Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bdbcede
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014, burrito development team.
+All rights reserved.
+
+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 names burrito or biocore 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c3c8e31
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include CHANGELOG.md
+include LICENSE
+include README.rst
+
+graft burrito
+
+global-exclude *.pyc
+global-exclude *.pyo
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..9d43f58
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,69 @@
+Metadata-Version: 1.1
+Name: burrito
+Version: 0.9.1
+Summary: Framework for wrapping and controlling command-line applications.
+Home-page: https://github.com/biocore/burrito
+Author: burrito development team
+Author-email: gregcaporaso at gmail.com
+License: BSD
+Description: burrito
+ =======
+
+ |Build Status| |Coverage Status|
+
+ burrito, canonically pronounced *boar-eee-toe*, is a Python framework for
+ wrapping and controlling command-line applications.
+
+ What's with the name?
+ ---------------------
+
+ This tool allows developers to wrap command-line applications, just as burritos
+ wrap delicious foods. Both hide the potentially unsightly details.
+
+ Installation
+ ------------
+
+ To install burrito::
+
+ pip install burrito
+
+ Running the tests
+ -----------------
+
+ To run burrito's unit tests::
+
+ nosetests
+
+ The pre-history of burrito
+ --------------------------
+
+ burrito is derived from the `application controller framework <http://pycogent.org/examples/application_controller_framework.html>`__
+ code, which was originally added to `PyCogent <http://www.pycogent.org>`__ and
+ later moved to `scikit-bio <http://scikit-bio.org>`__. The contributors and/or
+ copyright holders have agreed to make the code they wrote for PyCogent
+ available under the BSD license. The original authors of the application
+ controller framework code in PyCogent are Greg Caporaso
+ (`@gregcaporaso <https://github.com/gregcaporaso>`__), Sandra Smit,
+ Micah Hamady, and Rob Knight (`@rob-knight <https://github.com/rob-knight>`__).
+
+ .. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master
+ :target: https://travis-ci.org/biocore/burrito
+ .. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png
+ :target: https://coveralls.io/r/biocore/burrito
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Operating System :: Unix
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS :: MacOS X
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..2c88172
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,44 @@
+burrito
+=======
+
+|Build Status| |Coverage Status|
+
+burrito, canonically pronounced *boar-eee-toe*, is a Python framework for
+wrapping and controlling command-line applications.
+
+What's with the name?
+---------------------
+
+This tool allows developers to wrap command-line applications, just as burritos
+wrap delicious foods. Both hide the potentially unsightly details.
+
+Installation
+------------
+
+To install burrito::
+
+ pip install burrito
+
+Running the tests
+-----------------
+
+To run burrito's unit tests::
+
+ nosetests
+
+The pre-history of burrito
+--------------------------
+
+burrito is derived from the `application controller framework <http://pycogent.org/examples/application_controller_framework.html>`__
+code, which was originally added to `PyCogent <http://www.pycogent.org>`__ and
+later moved to `scikit-bio <http://scikit-bio.org>`__. The contributors and/or
+copyright holders have agreed to make the code they wrote for PyCogent
+available under the BSD license. The original authors of the application
+controller framework code in PyCogent are Greg Caporaso
+(`@gregcaporaso <https://github.com/gregcaporaso>`__), Sandra Smit,
+Micah Hamady, and Rob Knight (`@rob-knight <https://github.com/rob-knight>`__).
+
+.. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master
+ :target: https://travis-ci.org/biocore/burrito
+.. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png
+ :target: https://coveralls.io/r/biocore/burrito
diff --git a/burrito.egg-info/PKG-INFO b/burrito.egg-info/PKG-INFO
new file mode 100644
index 0000000..9d43f58
--- /dev/null
+++ b/burrito.egg-info/PKG-INFO
@@ -0,0 +1,69 @@
+Metadata-Version: 1.1
+Name: burrito
+Version: 0.9.1
+Summary: Framework for wrapping and controlling command-line applications.
+Home-page: https://github.com/biocore/burrito
+Author: burrito development team
+Author-email: gregcaporaso at gmail.com
+License: BSD
+Description: burrito
+ =======
+
+ |Build Status| |Coverage Status|
+
+ burrito, canonically pronounced *boar-eee-toe*, is a Python framework for
+ wrapping and controlling command-line applications.
+
+ What's with the name?
+ ---------------------
+
+ This tool allows developers to wrap command-line applications, just as burritos
+ wrap delicious foods. Both hide the potentially unsightly details.
+
+ Installation
+ ------------
+
+ To install burrito::
+
+ pip install burrito
+
+ Running the tests
+ -----------------
+
+ To run burrito's unit tests::
+
+ nosetests
+
+ The pre-history of burrito
+ --------------------------
+
+ burrito is derived from the `application controller framework <http://pycogent.org/examples/application_controller_framework.html>`__
+ code, which was originally added to `PyCogent <http://www.pycogent.org>`__ and
+ later moved to `scikit-bio <http://scikit-bio.org>`__. The contributors and/or
+ copyright holders have agreed to make the code they wrote for PyCogent
+ available under the BSD license. The original authors of the application
+ controller framework code in PyCogent are Greg Caporaso
+ (`@gregcaporaso <https://github.com/gregcaporaso>`__), Sandra Smit,
+ Micah Hamady, and Rob Knight (`@rob-knight <https://github.com/rob-knight>`__).
+
+ .. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master
+ :target: https://travis-ci.org/biocore/burrito
+ .. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png
+ :target: https://coveralls.io/r/biocore/burrito
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Operating System :: Unix
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS :: MacOS X
diff --git a/burrito.egg-info/SOURCES.txt b/burrito.egg-info/SOURCES.txt
new file mode 100644
index 0000000..5413b23
--- /dev/null
+++ b/burrito.egg-info/SOURCES.txt
@@ -0,0 +1,16 @@
+CHANGELOG.md
+LICENSE
+MANIFEST.in
+README.rst
+setup.py
+burrito/__init__.py
+burrito/parameters.py
+burrito/util.py
+burrito.egg-info/PKG-INFO
+burrito.egg-info/SOURCES.txt
+burrito.egg-info/dependency_links.txt
+burrito.egg-info/requires.txt
+burrito.egg-info/top_level.txt
+burrito/tests/__init__.py
+burrito/tests/test_parameters.py
+burrito/tests/test_util.py
\ No newline at end of file
diff --git a/burrito.egg-info/dependency_links.txt b/burrito.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/burrito.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/burrito.egg-info/requires.txt b/burrito.egg-info/requires.txt
new file mode 100644
index 0000000..68c4778
--- /dev/null
+++ b/burrito.egg-info/requires.txt
@@ -0,0 +1,6 @@
+future
+
+[test]
+nose >= 0.10.1
+flake8
+coveralls
\ No newline at end of file
diff --git a/burrito.egg-info/top_level.txt b/burrito.egg-info/top_level.txt
new file mode 100644
index 0000000..49132e0
--- /dev/null
+++ b/burrito.egg-info/top_level.txt
@@ -0,0 +1 @@
+burrito
diff --git a/burrito/__init__.py b/burrito/__init__.py
new file mode 100644
index 0000000..7792d3a
--- /dev/null
+++ b/burrito/__init__.py
@@ -0,0 +1,9 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+__version__ = "0.9.1"
diff --git a/burrito/parameters.py b/burrito/parameters.py
new file mode 100644
index 0000000..60c74b7
--- /dev/null
+++ b/burrito/parameters.py
@@ -0,0 +1,492 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+from copy import deepcopy
+
+from collections import Mapping
+
+
+def is_not_None(x):
+ """Returns True if x is not None"""
+ return x is not None
+
+
+class ParameterError(ValueError):
+
+ """Error raised when field in parameter is bad"""
+ pass
+
+
+class FilePath(str):
+
+ """ Hold paths for proper handling
+
+ Paths in this sense are filenames, directory paths, or filepaths.
+ Some examples include:
+ file.txt
+ ./path/to/file.txt
+ ./path/to/dir/
+ /path/to/file.txt
+ .
+ /
+
+ The purpose of this class is to allow all paths to be handled the
+ same since they sometimes need to be treated differently than
+ simple strings. For example, if a path has a space in it, and it
+ is being passed to system, it needs to be wrapped in quotes. But,
+ you wouldn't want it as a string wrapped in quotes b/c, e.g.,
+ isabs('"/absolute/path"') == False, b/c the first char is a ", not
+ a /.
+
+ * This would make more sense to call Path, but that conflicts with
+ the ResultPath.Path attribute. I'm not sure what to do about this
+ and want to see what others think. Once finalized, a global
+ replace should take care of making the switch.
+
+ """
+ def __new__(cls, path):
+ try:
+ return str.__new__(cls, path.strip('"'))
+ except AttributeError:
+ return str.__new__(cls, '')
+
+ def __str__(self):
+ """ wrap self in quotes, or return the empty string if self == '' """
+ if self == '':
+ return ''
+ return ''.join(['"', self, '"'])
+
+ def __add__(self, other):
+ return FilePath(''.join([self, other]))
+
+
+class Parameter(object):
+
+ """Stores information regarding a parameter to an application.
+
+ An abstract class.
+ """
+
+ def __init__(self, Prefix, Name, Value=None, Delimiter=None,
+ Quote=None, IsPath=None):
+ """Initialize the Parameter object.
+
+ Prefix: the character(s) preceding the name of the parameter
+ (eg. '-' for a '-a' parameter)
+ Name: the name of the parameter (eg. 'a' for a '-a' parameter)
+ Value: the value of the parameter (eg. '9' in a '-t=9' parameter)
+ The value is also used in subclasses to turn parameters on and off
+ Delimiter: the character separating the identifier and the value,
+ (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter)
+ Quote: the character to use when quoting the value (eg. "\"" for
+ a '-l="hello" parameter). At this point asymmetrical quotes
+ are not possible (ie. [4])
+ WARNING: You must escape the quote in most cases.
+ IsPath: boolean indicating whether Value is a file path, and should
+ therefore be cast to a FilePath object
+ WARNING: Don't set Quote='"' and set IsPath=True. This
+ would result in two sets of double quotes being wrapped around
+ the path when it is printed, and the application would most
+ likely fail. We explicitly disallow this with:
+ if self.IsPath and self.Quote == '"': self.Quote = None
+
+ Id: The combination of Prefix and Name is called the identifier (Id)
+ of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for
+ a '-t=9' parameter)
+
+ This is intended to be an abstract class and has no use otherwise.
+ To subclass Parameter, the subclass should implement the
+ following methods:
+ __str__(): returns the parameter as a string when turned on,
+ or as an empty string when turned off
+ on(): turns the parameter on
+ isOn(): return True if a parameter is on, otherwise False
+ off(): turns the parameter off
+ isOff(): return True if a parameter is off, otherwise False
+
+ Whether a parameter is on or off can be specified in different
+ ways in subclasses, your isOn() and isOff() methods should define
+ this.
+
+ Optionally you can overwrite __init__, but you should be sure to
+ either call the superclass init or handle the setting of the
+ self._default attribute (or things will break!)
+
+ """
+ self.Name = Name
+ self.Prefix = Prefix
+ self.Delimiter = Delimiter
+ self.Quote = Quote
+ self.Value = Value
+ self.IsPath = IsPath
+ if self.IsPath and self.Quote == '"':
+ self.Quote = None
+
+ def _get_id(self):
+ """Construct and return the identifier"""
+ return ''.join(map(str,
+ filter(is_not_None,
+ [self.Prefix, self.Name])))
+
+ Id = property(_get_id)
+
+ def __eq__(self, other):
+ """Return True if two parameters are equal"""
+ return (self.IsPath == other.IsPath) and\
+ (self.Name == other.Name) and\
+ (self.Prefix == other.Prefix) and\
+ (self.Delimiter == other.Delimiter) and \
+ (self.Quote == other.Quote) and \
+ (self.Value == other.Value)
+
+ def __ne__(self, other):
+ """Return True if two parameters are not equal to each other"""
+ return not self == other
+
+
+class FlagParameter(Parameter):
+
+ """Stores information regarding a flag parameter to an application"""
+
+ def __init__(self, Prefix, Name, Value=False):
+ """Initialize a FlagParameter object
+
+ Prefix: the character(s) preceding the name of the parameter
+ (eg. '-' for a '-a' parameter)
+ Name: the name of the parameter (eg. 'a' for a '-a' parameter)
+ Value: determines whether the flag is turned on or not;
+ should be True to turn on, or False to turn off,
+ False by default
+
+ Id: The combination of Prefix and Name is called the identifier (Id)
+ of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for
+ a '-t=9' parameter)
+
+ Usage:
+ f = FlagParameter(Prefix='-',Name='a')
+ Parameter f is turned off by default, so it won't be used by
+ the application until turned on.
+
+ or f = FlagParameter(Prefix='+',Name='d',Value=True)
+ Parameter f is turned on. It will be used by the application.
+ """
+ super(FlagParameter, self).__init__(Name=Name, Prefix=Prefix,
+ Value=Value, Delimiter=None,
+ Quote=None)
+
+ def __str__(self):
+ """Return the parameter as a string.
+
+ When turned on: string representation of the parameter
+ When turned off: empty string
+ """
+ if self.isOff():
+ return ''
+ else:
+ return ''.join(map(str, [self.Prefix, self.Name]))
+
+ def isOn(self):
+ """Returns True if the FlagParameter is turned on.
+
+ A FlagParameter is turned on if its Value is True or evaluates to True.
+ A FlagParameter is turned off if its Value is False or evaluates
+ to False.
+ """
+ if self.Value:
+ return True
+ return False
+
+ def isOff(self):
+ """Returns True if the parameter is turned off
+
+ A FlagParameter is turned on if its Value is True or evaluates to True.
+ A FlagParameter is turned off if its Value is False or evaluates
+ to False.
+ """
+ return not self.isOn()
+
+ def on(self):
+ """Turns the FlagParameter ON by setting its Value to True"""
+ self.Value = True
+
+ def off(self):
+ """Turns the FlagParameter OFF by setting its Value to False"""
+ self.Value = False
+
+
+class ValuedParameter(Parameter):
+
+ """Stores information regarding a valued parameter to an application"""
+
+ def __init__(self, Prefix, Name, Value=None, Delimiter=None, Quote=None,
+ IsPath=False):
+ """Initialize a ValuedParameter object.
+
+ Prefix: the character(s) preceding the name of the parameter
+ (eg. '-' for a '-a' parameter)
+ Name: the name of the parameter (eg. 'a' for a '-a' parameter)
+ Value: the value of the parameter (eg. '9' in a '-t=9' parameter)
+ Delimiter: the character separating the identifier and the value,
+ (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter)
+ Quote: the character to use when quoting the value (eg. "\"" for
+ a '-l="hello" parameter). At this point asymmetrical quotes
+ are not possible (ie. [4])
+ WARNING: You must escape the quote in most cases.
+ IsPath: boolean indicating whether Value is a file path, and should
+ therefore be cast to a FilePath object
+
+ Id: The combination of Prefix and Name is called the identifier (Id)
+ of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for
+ a '-t=9' parameter)
+ Default: the default value of the parameter; this is defined as
+ what is passed into init for Value and can not be changed
+ after object initialization
+
+ Usage:
+ v = ValuedParameter(Prefix='-',Name='a',Delimiter=' ',Value=3)
+ the parameter is turned on by default (value=3) and will be
+ used by the application as '-a 3'.
+ or v = ValuedParameter(Prefix='-',Name='d',Delimiter='=')
+ the parameter is turned off by default and won't be used by
+ the application unless turned on with some value.
+ """
+ if IsPath and Value:
+ Value = FilePath(Value)
+ super(ValuedParameter, self).__init__(Name=Name, Prefix=Prefix,
+ Value=Value,
+ Delimiter=Delimiter,
+ Quote=Quote,
+ IsPath=IsPath)
+ self._default = Value
+
+ def __str__(self):
+ """Return the parameter as a string
+
+ When turned on: string representation of the parameter
+ When turned off: empty string
+ """
+ if self.isOff():
+ return ''
+ else:
+ parts = [self.Prefix, self.Name, self.Delimiter,
+ self.Quote, self.Value, self.Quote]
+ return ''.join(map(str, filter(is_not_None, parts)))
+
+ def __eq__(self, other):
+ """Return True if two parameters are equal"""
+ return (self.Name == other.Name) and\
+ (self.Prefix == other.Prefix) and\
+ (self.Delimiter == other.Delimiter) and \
+ (self.Quote == other.Quote) and \
+ (self.Value == other.Value) and\
+ (self._default == other._default)
+
+ def _get_default(self):
+ """Get the default value of the ValuedParameter
+
+ Accessed as a property to avoid the user changing this
+ after initialization.
+ """
+ return self._default
+
+ Default = property(_get_default)
+
+ def reset(self):
+ """Reset Value of the ValuedParameter to the default"""
+ self.Value = self._default
+
+ def isOn(self):
+ """Returns True if the ValuedParameter is turned on
+
+ A ValuedParameter is turned on if its Value is not None.
+ A ValuedParameter is turned off if its Value is None.
+ """
+ if self.Value is not None:
+ return True
+ return False
+
+ def isOff(self):
+ """Returns True if the ValuedParameter is turned off
+
+ A ValuedParameter is turned on if its Value is not None.
+ A ValuedParameter is turned off if its Value is None.
+ """
+ return not self.isOn()
+
+ def on(self, val):
+ """Turns the ValuedParameter ON by setting its Value to val
+
+ An attempt to turn the parameter on with value 'None' will result
+ in an error, since this is the same as turning the parameter off.
+ """
+ if val is None:
+ raise ParameterError("Turning the ValuedParameter on with value "
+ "None is the same as turning it off. "
+ "Use another value.")
+ elif self.IsPath:
+ self.Value = FilePath(val)
+ else:
+ self.Value = val
+
+ def off(self):
+ """Turns the ValuedParameter OFF by setting its Value to None"""
+ self.Value = None
+
+
+class MixedParameter(ValuedParameter):
+
+ """Stores information regarding a mixed parameter to an application
+
+ A mixed parameter is a mix between a FlagParameter and a ValuedParameter.
+ When its Value is False, the parameter will be turned off.
+ When its Value is set to None, the parameter will behave like a flag.
+ When its Value is set to anything but None or False, it will behave
+ like a ValuedParameter.
+
+ Example: RNAfold [-d[0|1]]
+ You can give either '-d' or '-d0' or '-d1' as input.
+ """
+
+ def __init__(self, Prefix, Name, Value=False, Delimiter=None, Quote=None,
+ IsPath=False):
+ """Initialize a MixedParameter object
+
+ Prefix: the character(s) preceding the name of the parameter
+ (eg. '-' for a '-a' parameter)
+ Name: the name of the parameter (eg. 'a' for a '-a' parameter)
+ Value: the value of the parameter (eg. '9' in a '-t=9' parameter)
+ Delimiter: the character separating the identifier and the value,
+ (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter)
+ Quote: the character to use when quoting the value (eg. "\"" for
+ a '-l="hello" parameter). At this point asymmetrical quotes
+ are not possible (ie. [4])
+ WARNING: You must escape the quote in most cases.
+ IsPath: boolean indicating whether Value is a file path, and should
+ therefore be cast to a FilePath object
+
+ Id: The combination of Prefix and Name is called the identifier (Id)
+ of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for
+ a '-t=9' parameter)
+ Default: the default value of the parameter; this is defined as
+ what is passed into init for Value and can not be changed
+ after object initialization
+
+ Usage:
+ m = MixedParameter(Prefix='-',Name='a',Delimiter=' ',Value=3)
+ the parameter is turned on by default (value=3) and will be
+ used by the application as '-a 3'.
+ or m = MixedParameter(Prefix='-',Name='d',Delimiter='=',Value=None)
+ the parameter is turned on by default as a flag parameter and
+ will be used by the application as '-d'.
+ or m = MixedParameter(Prefix='-',Name='d',Delimiter='=')
+ the parameter is turned off by default (Value=False) and won't be
+ used by the application unless turned on with some value.
+ """
+ if IsPath and Value:
+ Value = FilePath(Value)
+ super(MixedParameter, self).__init__(Name=Name, Prefix=Prefix,
+ Value=Value, Delimiter=Delimiter,
+ Quote=Quote, IsPath=IsPath)
+
+ def __str__(self):
+ """Return the parameter as a string
+
+ When turned on: string representation of the parameter
+ When turned off: empty string
+ """
+ if self.isOff():
+ return ''
+ elif self.Value is None:
+ return ''.join(map(str, [self.Prefix, self.Name]))
+ else:
+ parts = [self.Prefix, self.Name, self.Delimiter,
+ self.Quote, self.Value, self.Quote]
+ return ''.join(map(str, filter(is_not_None, parts)))
+
+ def isOn(self):
+ """Returns True if the MixedParameter is turned on
+
+ A MixedParameter is turned on if its Value is not False.
+ A MixedParameter is turned off if its Value is False.
+
+ A MixedParameter is used as flag when its Value is None.
+ A MixedParameter is used as ValuedParameter when its Value is
+ anything but None or False.
+ """
+ if self.Value is not False:
+ return True
+ return False
+
+ def isOff(self):
+ """Returns True if the MixedParameter is turned off
+
+ A MixedParameter is turned on if its Value is not False.
+ A MixedParameter is turned off if its Value is False.
+ """
+ return not self.isOn()
+
+ def on(self, val=None):
+ """Turns the MixedParameter ON by setting its Value to val
+
+ An attempt to turn the parameter on with value 'False' will result
+ in an error, since this is the same as turning the parameter off.
+
+ Turning the MixedParameter ON without a value or with value 'None'
+ will let the parameter behave as a flag.
+ """
+ if val is False:
+ raise ParameterError("Turning the ValuedParameter on with value "
+ "False is the same as turning it off. Use "
+ "another value.")
+ elif self.IsPath:
+ self.Value = FilePath(val)
+ else:
+ self.Value = val
+
+ def off(self):
+ """Turns the MixedParameter OFF by setting its Value to False"""
+ self.Value = False
+
+
+class Parameters(Mapping):
+
+ """Parameters is a dictionary of Parameter objects.
+
+ Parameters provides a mask that lets the user lookup and access parameters
+ by its synonyms.
+ """
+
+ def __init__(self, parameters={}, synonyms={}):
+ """Initialize the Parameters object.
+
+ parameters: a dictionary of Parameter objects keyed by their identifier
+ synonyms: a dictionary of synonyms. Keys are synonyms, values are
+ parameter identifiers.
+ """
+ self._parameters = deepcopy(parameters)
+ self._synonyms = deepcopy(synonyms)
+
+ def __len__(self):
+ return len(self._parameters)
+
+ def __iter__(self):
+ return iter(self._parameters)
+
+ def __getitem__(self, key):
+ try:
+ key = self._synonyms[key]
+ except KeyError:
+ # the key is not a synonym
+ pass
+
+ return self._parameters[key]
+
+ def all_off(self):
+ """Turns all parameters in the dictionary off"""
+ for v in self._parameters.values():
+ v.off()
diff --git a/burrito/tests/__init__.py b/burrito/tests/__init__.py
new file mode 100644
index 0000000..5d6022a
--- /dev/null
+++ b/burrito/tests/__init__.py
@@ -0,0 +1,7 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
diff --git a/burrito/tests/test_parameters.py b/burrito/tests/test_parameters.py
new file mode 100644
index 0000000..28e9b17
--- /dev/null
+++ b/burrito/tests/test_parameters.py
@@ -0,0 +1,659 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+from unittest import TestCase, main
+from burrito.parameters import (FlagParameter, ValuedParameter, MixedParameter,
+ Parameters, ParameterError, FilePath)
+
+
+class FlagParameterTests(TestCase):
+
+ """ Tests of the FlagParameter class """
+
+ def setUp(self):
+ """Setup some variables for the tests to use """
+ self.p_modify_prefix = [FlagParameter(Name='d', Prefix='-'),
+ FlagParameter(Name='d', Prefix='--'),
+ FlagParameter(Name='d', Prefix='')]
+
+ self.p_modify_name = [FlagParameter(Name='d', Prefix='-'),
+ FlagParameter(Name='D', Prefix='-'),
+ FlagParameter(Name=4, Prefix='-'),
+ FlagParameter(Name='abcdef', Prefix='-')]
+
+ self.p_On = [FlagParameter(Name='d', Prefix='-', Value=True),
+ FlagParameter(Name='d', Prefix='-', Value=5),
+ FlagParameter(Name='d', Prefix='-', Value=[1]),
+ FlagParameter(Name='d', Prefix='-', Value='F')]
+
+ self.p_Off = [FlagParameter(Name='d', Prefix='-', Value=False),
+ FlagParameter(Name='d', Prefix='-', Value=None),
+ FlagParameter(Name='d', Prefix='-', Value=[]),
+ FlagParameter(Name='d', Prefix='-', Value=0),
+ FlagParameter(Name='d', Prefix='-', Value='')]
+
+ self.ID_tests = [FlagParameter(Name='d', Prefix='-'),
+ FlagParameter(Name='d', Prefix=''),
+ FlagParameter(Name='', Prefix='-'),
+ FlagParameter(Name=4, Prefix='-'),
+ FlagParameter(Name=None, Prefix='-'),
+ FlagParameter(Name=4, Prefix=None),
+ FlagParameter(Name='abcdef', Prefix='-')]
+
+ def test_init(self):
+ """FlagParameter: init functions as expected """
+ param = FlagParameter(Name='a', Prefix='-', Value=42)
+ self.assertEqual(param.Name, 'a')
+ self.assertEqual(param.Prefix, '-')
+ self.assertEqual(param.Value, 42)
+ self.assertEqual(param.Delimiter, None)
+ self.assertEqual(param.Quote, None)
+ self.assertEqual(param.Id, '-a')
+
+ def test_init_defaults(self):
+ """FlagParameter: init functions as expected with default values"""
+ p = FlagParameter(Name='a', Prefix='-')
+ self.assertEqual(p.Name, 'a')
+ self.assertEqual(p.Prefix, '-')
+ self.assertEqual(p.Value, False)
+ self.assertEqual(p.Delimiter, None)
+ self.assertEqual(p.Quote, None)
+ self.assertEqual(p.Id, '-a')
+
+ def test_get_id(self):
+ """FlagParameter: _get_id functions as expected """
+
+ expected_results = ['-d', 'd', '-', '-4', '-', '4', '-abcdef']
+
+ for param, exp in zip(self.ID_tests, expected_results):
+ self.assertEqual(param._get_id(), exp)
+
+ def test_eq(self):
+ """FlagParameter: eq functions as expected """
+ p1 = FlagParameter(Name='a', Prefix='-', Value=True)
+ p2 = FlagParameter(Name='a', Prefix='-', Value=True)
+ p3 = FlagParameter(Name='a', Prefix='-')
+ p4 = FlagParameter(Name='i', Prefix='-', Value=True)
+ p5 = FlagParameter(Name='a', Prefix='--', Value=True)
+
+ assert p1 == p2
+ assert not p1 == p3
+ assert not p1 == p4
+ assert not p1 == p5
+ assert not p3 == p4
+ assert not p3 == p5
+ assert not p4 == p5
+
+ def test_ne(self):
+ """FlagParameter: ne functions as expected """
+ p1 = FlagParameter(Name='a', Prefix='-', Value=True)
+ p2 = FlagParameter(Name='a', Prefix='-', Value=True)
+ p3 = FlagParameter(Name='a', Prefix='-')
+ p4 = FlagParameter(Name='i', Prefix='-', Value=True)
+ p5 = FlagParameter(Name='a', Prefix='--', Value=True)
+
+ assert not p1 != p2
+ assert p1 != p3
+ assert p1 != p4
+ assert p1 != p5
+ assert p3 != p4
+ assert p3 != p5
+ assert p4 != p5
+
+ def test_isOn_True(self):
+ """FlagParameter: isOn functions as expected with True Values """
+ for param in self.p_On:
+ assert param.isOn()
+
+ def test_isOn_False(self):
+ """FlagParameter: isOn functions as expected with False Values """
+ for param in self.p_Off:
+ assert not param.isOn()
+
+ def test_isOff_True(self):
+ """FlagParameter: isOff functions as expected with True values """
+ for param in self.p_Off:
+ assert param.isOff()
+
+ def test_isOff_False(self):
+ """FlagParameter: isOff functions as expected with False values """
+ for param in self.p_On:
+ assert not param.isOff()
+
+ def test_on(self):
+ """FlagParameter: on functions as expected """
+ for param in self.p_On + self.p_Off:
+ param.on()
+ assert param.isOn()
+
+ def test_off(self):
+ """FlagParameter: off functions as expected """
+ for param in self.p_On + self.p_Off:
+ param.off()
+ assert param.isOff()
+
+ def test_str_modify_prefix(self):
+ """FlagParameter: str functions as expected with different prefixes """
+
+ expected_results = ['-d', '--d', 'd']
+
+ for param, exp in zip(self.p_modify_prefix, expected_results):
+ param.on()
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_name(self):
+ """FlagParameter: str functions as expected with different names """
+
+ expected_results = ['-d', '-D', '-4', '-abcdef']
+
+ for param, exp in zip(self.p_modify_name, expected_results):
+ param.on()
+ self.assertEqual(str(param), exp)
+
+
+class ValuedParameterTests(TestCase):
+
+ """ Tests of the ValuedParameter class """
+ constructor = ValuedParameter
+ s = 'Valued'
+
+ def setUp(self):
+ """Setup some variables for the tests to use """
+ self.p_modify_prefix = [self.constructor(Name='d', Prefix='-'),
+ self.constructor(Name='d', Prefix='--'),
+ self.constructor(Name='d', Prefix='')]
+
+ self.p_modify_name = [self.constructor(Name='d', Prefix='-'),
+ self.constructor(Name='D', Prefix='-'),
+ self.constructor(Name=4, Prefix='-'),
+ self.constructor(Name='abcdef', Prefix='-')]
+
+ self.p_On = [self.constructor(Name='d', Prefix='-', Value=True),
+ self.constructor(Name='d', Prefix='-', Value=5),
+ self.constructor(Name='d', Prefix='-', Value=[1]),
+ self.constructor(Name='d', Prefix='-', Value=False),
+ self.constructor(Name='d', Prefix='-', Value='F')]
+
+ self.p_Off = [self.constructor(Name='d', Prefix='-', Value=None)]
+
+ self.p_full = [self.constructor(Name='a', Prefix='-',
+ Value=42, Delimiter=' ', Quote='\'')]
+
+ self.p_default = [self.constructor(Name='a', Prefix='-')]
+
+ self.p_modified_prefix = [self.constructor(Name='d', Prefix='-'),
+ self.constructor(Name='d', Prefix='--'),
+ self.constructor(Name='d', Prefix='')]
+
+ self.p_modified_name = [self.constructor(Name='d', Prefix='-'),
+ self.constructor(Name='D', Prefix='-'),
+ self.constructor(Name=4, Prefix='-'),
+ self.constructor(Name='abcdef', Prefix='-')]
+
+ self.p_modified_delimiter =\
+ [self.constructor(Name='d', Prefix='-', Value=42),
+ self.constructor(Name='d', Prefix='-', Value=42, Delimiter=''),
+ self.constructor(Name='d', Prefix='-', Value=42, Delimiter=' '),
+ self.constructor(Name='d', Prefix='-', Value=42, Delimiter=9),
+ self.constructor(Name='d', Prefix='-', Value=42, Delimiter='=')]
+
+ self.p_modified_value =\
+ [self.constructor(Name='d', Prefix='-', Value=42, Delimiter=' '),
+ self.constructor(
+ Name='d',
+ Prefix='-',
+ Value='pbl',
+ Delimiter=' '),
+ self.constructor(
+ Name='d',
+ Prefix='-',
+ Value='2-2',
+ Delimiter=' '),
+ self.constructor(Name='d', Prefix='-', Value='evo/t.txt',
+ Delimiter=' '),
+ self.constructor(Name='d', Prefix='-', Value='\'',
+ Delimiter=' ')]
+
+ self.p_modified_quote =\
+ [self.constructor(Name='d', Prefix='-', Value=42, Quote=''),
+ self.constructor(Name='d', Prefix='-', Value=42),
+ self.constructor(Name='d', Prefix='-', Value=42, Quote=' '),
+ self.constructor(Name='d', Prefix='-', Value=42, Quote='\''),
+ self.constructor(Name='d', Prefix='-', Value=42, Quote='\"'),
+ self.constructor(Name='d', Prefix='-', Value=42, Quote='x')]
+
+ self.ID_tests = [self.constructor(Name='d', Prefix='-'),
+ self.constructor(Name='d', Prefix=''),
+ self.constructor(Name='', Prefix='-'),
+ self.constructor(Name=4, Prefix='-'),
+ self.constructor(Name=None, Prefix='-'),
+ self.constructor(Name=4, Prefix=None),
+ self.constructor(Name='abcdef', Prefix='-')]
+
+ self.p_modified_is_path =\
+ [self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', IsPath=True),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', IsPath=False),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', Quote='"', IsPath=True)]
+
+ def test_init(self):
+ """Parameter: init functions as expected """
+
+ for param in self.p_full:
+ self.assertEqual(param.Name, 'a')
+ self.assertEqual(param.Prefix, '-')
+ self.assertEqual(param.Value, 42)
+ self.assertEqual(param.Delimiter, ' ')
+ self.assertEqual(param.Quote, '\'')
+ self.assertEqual(param.Id, '-a')
+
+ def test_init_defaults(self):
+ """Parameter: init functions as expected with default values"""
+ for p in self.p_default:
+ self.assertEqual(p.Name, 'a')
+ self.assertEqual(p.Prefix, '-')
+ self.assertEqual(p.Value, None)
+ self.assertEqual(p.Delimiter, None)
+ self.assertEqual(p.Quote, None)
+ self.assertEqual(p.Id, '-a')
+
+ def test_get_id(self):
+ """Parameter: _get_id functions as expected """
+
+ expected_results = ['-d', 'd', '-', '-4', '-', '4', '-abcdef']
+
+ for param, exp in zip(self.ID_tests, expected_results):
+ self.assertEqual(param._get_id(), exp)
+
+ def test_eq(self):
+ """Parameter: eq functions as expected """
+ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p2 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p3 = self.constructor(Name='dsf', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p4 = self.constructor(Name='a', Prefix='--', Value=42, Quote='\'',
+ Delimiter='=')
+ p5 = self.constructor(Name='a', Prefix='-', Value=942, Quote='\'',
+ Delimiter='=')
+ p6 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\"',
+ Delimiter='=')
+ p7 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='!!!')
+ p8 = self.constructor(Name='wwwww', Prefix='-------')
+ p9 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=', IsPath=True)
+
+ assert p1 == p2
+ assert not p1 == p3
+ assert not p1 == p4
+ assert not p1 == p5
+ assert not p1 == p6
+ assert not p1 == p7
+ assert not p1 == p8
+ assert not p1 == p9
+ # test default setting
+ p5.Value = 42
+ assert not p1 == p5
+
+ def test_ne(self):
+ """Parameter: ne functions as expected """
+ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p2 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p3 = self.constructor(Name='dsf', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p4 = self.constructor(Name='a', Prefix='--', Value=42, Quote='\'',
+ Delimiter='=')
+ p5 = self.constructor(Name='a', Prefix='-', Value=942, Quote='\'',
+ Delimiter='=')
+ p6 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\"',
+ Delimiter='=')
+ p7 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='!!!')
+ p8 = self.constructor(Name='wwwww', Prefix='-------')
+ p9 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=', IsPath=True)
+
+ assert not p1 != p2
+ assert p1 != p3
+ assert p1 != p4
+ assert p1 != p5
+ assert p1 != p6
+ assert p1 != p7
+ assert p1 != p8
+ assert p1 != p9
+ # test default setting
+ p5.Value = 42
+ assert p1 != p5
+
+ def test_get_default(self):
+ """Parameter: default behaves as expected """
+ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ self.assertEqual(p1._get_default(), 42)
+ p1.Value = 43
+ self.assertEqual(p1._get_default(), 42)
+
+ def test_get_default_w_IsPath(self):
+ """Parameter: default is a FilePath object when IsPath is set """
+ p = self.constructor(
+ Name='a', Prefix='-', Value='test.txt', Quote='\'',
+ Delimiter='=', IsPath=True)
+ self.assertEqual(p._get_default(), 'test.txt')
+ self.assertEqual(p.Default, 'test.txt')
+ p.Value = 'test2.txt'
+ self.assertEqual(p._get_default(), 'test.txt')
+ self.assertEqual(p.Default, 'test.txt')
+ assert isinstance(p._get_default(), FilePath)
+ assert isinstance(p.Default, FilePath)
+
+ def test_reset(self):
+ """Parameter: reset correctly set Value to _default """
+ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'',
+ Delimiter='=')
+ p1.Value = 43
+ self.assertNotEqual(p1.Default, p1.Value)
+ p1.reset()
+ self.assertEqual(p1.Default, p1.Value)
+
+ def test_isOn_True(self):
+ """Parameter: isOn functions as expected with True Values """
+ for param in self.p_On:
+ assert param.isOn()
+
+ def test_isOn_False(self):
+ """Parameter: isOn functions as expected with False Values """
+ for param in self.p_Off:
+ assert not param.isOn()
+
+ def test_isOff_True(self):
+ """Parameter: isOff functions as expected with True values """
+ for param in self.p_Off:
+ assert param.isOff()
+
+ def test_isOff_False(self):
+ """Parameter: isOff functions as expected with False values """
+ for param in self.p_On:
+ assert not param.isOff()
+
+ def test_on(self):
+ """Parameter: on functions as expected """
+ for param in self.p_On + self.p_Off:
+ param.on('a')
+ assert param.isOn()
+ p = self.p_On[0]
+ self.assertRaises(ParameterError, p.on, None)
+
+ def test_off(self):
+ """Parameter: off functions as expected """
+ for param in self.p_On + self.p_Off:
+ param.off()
+ assert param.isOff()
+
+ def test_str_off(self):
+ """Parameter: str() prints empty string when off """
+ for p in self.p_Off:
+ self.assertEqual(str(p), '')
+
+ def test_str_modify_prefix(self):
+ """Parameter: str functions as expected with different prefixes """
+
+ expected_results = ['-d', '--d', 'd']
+
+ for param, exp in zip(self.p_modified_prefix, expected_results):
+ param.on('')
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_name(self):
+ """Parameter: str functions as expected with different names """
+
+ expected_results = ['-d', '-D', '-4', '-abcdef']
+
+ for param, exp in zip(self.p_modified_name, expected_results):
+ param.on('')
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_delimiter(self):
+ """Parameter: str functions as expected with different delimiter """
+
+ expected_results = ['-d42', '-d42', '-d 42', '-d942', '-d=42']
+
+ for param, exp in zip(self.p_modified_delimiter, expected_results):
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_values(self):
+ """Parameter: str functions as expected with different values """
+
+ expected_results = ['-d 42',
+ '-d pbl', '-d 2-2', '-d evo/t.txt', '-d \'']
+
+ for param, exp in zip(self.p_modified_value, expected_results):
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_quotes(self):
+ """Parameter: str functions as expected with different quotes """
+
+ expected_results = ['-d42', '-d42', '-d 42 ', '-d\'42\'',
+ '-d\"42\"', '-dx42x']
+
+ for param, exp in zip(self.p_modified_quote, expected_results):
+ self.assertEqual(str(param), exp)
+
+ def test_str_modify_is_path(self):
+ """Parameter: str functions as expected with different IsPath """
+
+ expected_results = ['-d "test.txt"', '-d test.txt', '-d "test.txt"']
+
+ for param, exp in zip(self.p_modified_is_path, expected_results):
+ self.assertEqual(str(param), exp)
+
+ def test_str_full(self):
+ """Parameter: str functions as expected with all values non-default """
+ for p in self.p_full:
+ self.assertEqual(str(p), '-a \'42\'')
+
+
+class MixedParameterTests(ValuedParameterTests):
+
+ """ Tests of the MixedParameter class """
+
+ constructor = MixedParameter
+
+ def setUp(self):
+ """Setup some variables for the tests to use """
+ super(MixedParameterTests, self).setUp()
+ self.p_On = [self.constructor(Name='d', Prefix='-', Value=True),
+ self.constructor(Name='d', Prefix='-', Value=5),
+ self.constructor(Name='d', Prefix='-', Value=[1]),
+ self.constructor(Name='d', Prefix='-', Value=None),
+ self.constructor(Name='d', Prefix='-', Value='F')]
+
+ self.p_Off = [self.constructor(Name='d', Prefix='-', Value=False)]
+
+ # This is different from the superclass variable b/c we need to make
+ # sure that specifying IsPath with Value=None functions as expected
+ self.p_modified_is_path =\
+ [self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', IsPath=True),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', Quote='"', IsPath=True),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value='test.txt', IsPath=False),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value=None, IsPath=True),
+ self.constructor(Name='d', Prefix='-', Delimiter=' ',
+ Value=None, IsPath=False)]
+
+ def test_on(self):
+ """Parameter: on functions as expected """
+ for param in self.p_On + self.p_Off:
+ param.on('a')
+ assert param.isOn()
+ p = self.p_On[0]
+ self.assertRaises(ParameterError, p.on, False)
+
+ def test_init_defaults(self):
+ """MixedParameter: init functions as expected with default values"""
+ for p in self.p_default:
+ self.assertEqual(p.Name, 'a')
+ self.assertEqual(p.Prefix, '-')
+ self.assertEqual(p.Value, False)
+ self.assertEqual(p.Delimiter, None)
+ self.assertEqual(p.Quote, None)
+ self.assertEqual(p.Id, '-a')
+ self.assertEqual(p.IsPath, False)
+
+ def test_str_all_modes(self):
+ """MixedParameter: str() functions in various modes """
+ p = MixedParameter(Prefix='-', Name='d', Delimiter='=', Quote=']')
+ self.assertEqual(str(p), '')
+ p.on()
+ self.assertEqual(str(p), '-d')
+ p.on('a')
+ self.assertEqual(str(p), '-d=]a]')
+
+ def test_str_modify_is_path(self):
+ """MixedParameter: str functions as expected with different IsPath """
+
+ # This is different from the superclass test b/c we need to make
+ # sure that specifying IsPath with Value=None functions as expected
+ expected_results = ['-d "test.txt"', '-d "test.txt"',
+ '-d test.txt', '-d', '-d']
+
+ for param, exp in zip(self.p_modified_is_path, expected_results):
+ self.assertEqual(str(param), exp)
+
+
+class ParametersTests(TestCase):
+
+ """Tests of the Parameters class"""
+
+ def setUp(self):
+ self.fp = FlagParameter(Prefix='-', Name='d')
+ self.vp = ValuedParameter(Name='p', Prefix='-', Value=[1])
+ self.mp = MixedParameter(Prefix='--', Name='k', Delimiter=' ')
+ self.all_params = {self.fp.Id: self.fp, self.vp.Id: self.vp,
+ self.mp.Id: self.mp}
+ self.p1 = Parameters()
+ self.p2 = Parameters(self.all_params)
+ self._synonyms = {'Pino': '-p', 'K': 'k'}
+ self.p3 = Parameters(self.all_params, self._synonyms)
+
+ def test_init(self):
+ """Parameters: init functions as expected"""
+ self.assertEqual(self.p1, {})
+ self.assertEqual(self.p2, self.all_params)
+ self.assertEqual(self.p3, self.all_params)
+
+ def test_lookup(self):
+ """Parameters: test ability to lookup """
+ self.assertEqual(self.p2['-p'], self.vp)
+ self.assertEqual(self.p3['Pino'], self.vp)
+
+ def test_immutability(self):
+ """Parameters: attempt to modify object raises error """
+ try:
+ self.p2['-p'] = 42
+ except TypeError:
+ pass
+ else:
+ raise AttributeError("Parameters shouldn't support assignment.")
+
+ try:
+ del self.p2['-p']
+ except TypeError:
+ pass
+ else:
+ raise AttributeError("Parameters shouldn't support deletion.")
+
+ def test_all_off(self):
+ """Parameters: all_off() should turn all parameters off"""
+ p = self.p2
+ # turn everything on
+ for v in p.values():
+ try:
+ v.on(3)
+ except TypeError:
+ v.on()
+ self.assertTrue(v.isOn())
+
+ # turn everything off
+ p.all_off()
+ for v in p.values():
+ self.assertTrue(v.isOff())
+
+
+class FilePathTests(TestCase):
+
+ """ Tests of the FilePath class """
+
+ def setUp(self):
+ """ Initialize variables to be used by tests """
+ self.filename = 'filename.txt'
+ self.relative_dir_path = 'a/relative/path/'
+ self.relative_dir_path_no_trailing_slash = 'a/relative/path'
+ self.relative_file_path = 'a/relative/filepath.txt'
+ self.absolute_dir_path = '/absolute/path/'
+ self.absolute_file_path = '/absolute/filepath.txt'
+ self.all_paths = [self.filename, self.relative_dir_path,
+ self.relative_file_path, self.absolute_dir_path,
+ self.absolute_file_path]
+
+ def test_init(self):
+ """FilePath: initialization returns w/o error """
+ for p in self.all_paths:
+ self.assertEqual(FilePath(p), p)
+ self.assertEqual(FilePath(''), '')
+
+ def test_str(self):
+ """FilePath: str wraps path in quotes """
+ # Do one explicit test (for sanity), then automatically run
+ # through the examples
+ self.assertEqual(str(FilePath(self.filename)), '"filename.txt"')
+ for p in self.all_paths:
+ self.assertEqual(str(FilePath(p)), '"' + p + '"')
+
+ def test_str_path_is_None(self):
+ """FilePath: str return empty string when path is None """
+ self.assertEqual(str(FilePath(None)), '')
+
+ def test_add(self):
+ """FilePath: add (or joining of paths) functions as expected """
+ actual = FilePath(self.relative_dir_path) + FilePath(self.filename)
+ expected = FilePath('a/relative/path/filename.txt')
+ self.assertEqual(actual, expected)
+ # result is a FilePath
+ assert isinstance(actual, FilePath)
+ # appending a string to a FilePath results in a FilePath
+ actual = FilePath(self.relative_dir_path) + 'filename.txt'
+ expected = FilePath('a/relative/path/filename.txt')
+ self.assertEqual(actual, expected)
+ # result is a FilePath
+ assert isinstance(actual, FilePath)
+
+ def test_FilePath_identity_preserved(self):
+ """FilePath: trivial actions on FilePaths yeild original FilePath
+ """
+ p = FilePath(self.filename)
+ # Creating FilePath from FilePath results in FilePath
+ # equal to original
+ self.assertEqual(FilePath(p), p)
+ for p in self.all_paths:
+ self.assertEqual(FilePath(p), p)
+ # Appending an empty FilePath to a FilePath results in FilePath
+ # equal to original
+ self.assertEqual(p + FilePath(''), p)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/burrito/tests/test_util.py b/burrito/tests/test_util.py
new file mode 100644
index 0000000..d222e6e
--- /dev/null
+++ b/burrito/tests/test_util.py
@@ -0,0 +1,1313 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+from tempfile import gettempdir
+from os import remove, system, rmdir, getcwd, walk
+from os.path import exists
+from copy import deepcopy
+
+from burrito.parameters import (FilePath, ValuedParameter, FlagParameter,
+ MixedParameter)
+from unittest import TestCase
+from burrito.util import (CommandLineApplication, ResultPath, ApplicationError,
+ ParameterIterBase, ParameterCombinations,
+ cmdline_generator, ApplicationNotFoundError,
+ get_tmp_filename, guess_input_handler, which)
+
+
+class ParameterCombinationsTests(TestCase):
+
+ def setUp(self):
+ """Setup for ParameterCombinations tests"""
+ self.mock_app = ParameterCombinationsApp
+ self.params = {'-flag1': True,
+ '--value1': list(range(0, 5)),
+ '-delim': list(range(0, 2)),
+ '-mix1': [None] + list(range(0, 3))}
+ self.always_on = ['--value1']
+ self.param_iter = ParameterCombinations(self.mock_app, self.params,
+ self.always_on)
+
+ def test_init_generator(self):
+ """Tests generator capabilities"""
+ all_params = list(self.param_iter)
+ self.assertEqual(len(all_params), 150)
+ params = {'-flag1': True,
+ '--value1': 1,
+ '-delim': ['choice1', 'choice2']}
+ always_on = ['-flag1', '-delim']
+ param_iter = ParameterCombinations(self.mock_app, params, always_on)
+
+ exp = [deepcopy(self.mock_app._parameters),
+ deepcopy(self.mock_app._parameters),
+ deepcopy(self.mock_app._parameters),
+ deepcopy(self.mock_app._parameters)]
+
+ # default is on in all these cases
+ exp[0]['-flag1'].on()
+ exp[0]['--value1'].on(1)
+ exp[0]['-delim'].on('choice1')
+
+ exp[1]['-flag1'].on()
+ exp[1]['--value1'].on(1)
+ exp[1]['-delim'].on('choice2')
+
+ exp[2]['-flag1'].on()
+ exp[2]['--value1'].off()
+ exp[2]['-delim'].on('choice1')
+
+ exp[3]['-flag1'].on()
+ exp[3]['--value1'].off()
+ exp[3]['-delim'].on('choice2')
+
+ obs = list(param_iter)
+ self.assertEqual(obs, exp)
+
+ def test_reset(self):
+ """Resets the iterator"""
+ first = list(self.param_iter)
+ self.assertRaises(StopIteration, lambda: next(self.param_iter))
+ self.param_iter.reset()
+ second = list(self.param_iter)
+ self.assertEqual(first, second)
+
+
+class ParameterIterBaseTests(TestCase):
+
+ def setUp(self):
+ """Setup for ParameterIterBase tests"""
+ self.mock_app = ParameterCombinationsApp
+ self.params = {'-flag1': True,
+ '--value1': list(range(0, 5)),
+ '-delim': list(range(0, 2)),
+ '-mix1': [None] + list(range(0, 3))}
+ self.always_on = ['--value1']
+ self.param_base = ParameterIterBase(self.mock_app, self.params,
+ self.always_on)
+
+ def test_init(self):
+ """Test constructor"""
+ exp_params = {'-flag1': [True, False],
+ '--value1': list(range(0, 5)),
+ '-delim': list(range(0, 2)) + [False],
+ '-mix1': [None, 0, 1, 2] + [False]}
+
+ self.assertEqual(exp_params,
+ dict(zip(self.param_base._keys,
+ self.param_base._values)))
+
+ self.params['asdasda'] = 5
+ self.assertRaises(ValueError, ParameterIterBase, self.mock_app,
+ self.params, self.always_on)
+
+ self.params.pop('asdasda')
+ self.always_on.append('asdasd')
+ self.assertRaises(ValueError, ParameterIterBase, self.mock_app,
+ self.params, self.always_on)
+
+ def test_make_app_params(self):
+ """Returns app parameters with expected values set"""
+ values = [0, 0, True, None]
+ exp = deepcopy(self.mock_app._parameters)
+ exp['-flag1'].on()
+ exp['--value1'].on(0)
+ exp['-delim'].on(0)
+ exp['-mix1'].on(None)
+ obs = self.param_base._make_app_params(values)
+ self.assertEqual(obs, exp)
+
+ state = [4, False, False, False]
+ exp = deepcopy(self.mock_app._parameters)
+ exp['-flag1'].off()
+ exp['--value1'].on(4)
+ exp['-delim'].off()
+ exp['-mix1'].off()
+ obs = self.param_base._make_app_params(state)
+ self.assertEqual(obs, exp)
+
+
+class CommandLineGeneratorTests(TestCase):
+
+ def setUp(self):
+ self.abs_path_to_bin = '/bin/path'
+ self.abs_path_to_cmd = '/cmd/path'
+ self.abs_path_to_input = '/input/path'
+ self.abs_path_to_output = '/output/path'
+ self.abs_path_to_stdout = '/stdout/path'
+ self.abs_path_to_stderr = '/stderr/path'
+ self.app = ParameterCombinationsApp
+
+ params = {'-flag1': True,
+ '-delim': ['choice1', 'choice2']}
+ always_on = ['-delim']
+ self.mock_app = ParameterCombinationsApp
+ self.param_iter = ParameterCombinations(
+ self.mock_app,
+ params,
+ always_on)
+
+ def test_cmdline_generator_easy(self):
+ """Returns parameter combinations commandlines"""
+ cmdgen = cmdline_generator(self.param_iter,
+ PathToBin=self.abs_path_to_bin,
+ PathToCmd=self.abs_path_to_cmd,
+ PathsToInputs=self.abs_path_to_input,
+ PathToOutput=self.abs_path_to_output,
+ PathToStdout=self.abs_path_to_stdout,
+ PathToStderr=self.abs_path_to_stderr,
+ UniqueOutputs=False,
+ InputParam='-input',
+ OutputParam='-output')
+ bin = self.abs_path_to_bin
+ cmd = self.abs_path_to_cmd
+ inputfile = self.abs_path_to_input
+ outputfile = self.abs_path_to_output
+ stdout = self.abs_path_to_stdout
+ stderr = self.abs_path_to_stderr
+
+ exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1',
+ '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr])]
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+
+ cmdlines = list(cmdgen)
+ self.assertEqual(cmdlines, exp)
+
+ def test_cmdline_generator_hard(self):
+ """Returns parameter combinations commandlines. Test stdin/stdout"""
+ cmdgen = cmdline_generator(self.param_iter,
+ PathToBin=self.abs_path_to_bin,
+ PathToCmd=self.abs_path_to_cmd,
+ PathsToInputs=self.abs_path_to_input,
+ PathToOutput=self.abs_path_to_output,
+ PathToStdout=self.abs_path_to_stdout,
+ PathToStderr=self.abs_path_to_stderr,
+ UniqueOutputs=True,
+ InputParam=None,
+ OutputParam=None)
+ bin = self.abs_path_to_bin
+ cmd = self.abs_path_to_cmd
+ inputfile = self.abs_path_to_input
+ outputfile = self.abs_path_to_output
+ stderr = self.abs_path_to_stderr
+
+ # the extra '' is intentionally added. When stdout is used for actual
+ # output, the stdout_ param gets set to '' which results in an extra
+ # space being generated on the cmdline. this should be benign
+ # across operating systems
+ exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1',
+ '< "%s"' % inputfile, '> "%s"0' % outputfile, '',
+ '2> "%s"' % stderr])]
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '< "%s"' % inputfile, '> "%s"1' % outputfile, '',
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '< "%s"' % inputfile,
+ '> "%s"2' % outputfile, '', '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '< "%s"' % inputfile, '> "%s"3' % outputfile, '',
+ '2> "%s"' % stderr]))
+
+ cmdlines = list(cmdgen)
+ self.assertEqual(cmdlines, exp)
+
+ def test_cmdline_generator_stdout_stderr_off(self):
+ """Returns cmdlines with stdout and stderr disabled"""
+ cmdgen = cmdline_generator(self.param_iter,
+ PathToBin=self.abs_path_to_bin,
+ PathToCmd=self.abs_path_to_cmd,
+ PathsToInputs=self.abs_path_to_input,
+ PathToOutput=self.abs_path_to_output,
+ PathToStdout=None,
+ PathToStderr=None,
+ UniqueOutputs=False,
+ InputParam='-input',
+ OutputParam='-output')
+ bin = self.abs_path_to_bin
+ cmd = self.abs_path_to_cmd
+ inputfile = self.abs_path_to_input
+ outputfile = self.abs_path_to_output
+
+ exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1',
+ '-input="%s"' % inputfile, '-output="%s"' % outputfile,
+ '', ''])]
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile,
+ '', '']))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile, '', '']))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-input="%s"' % inputfile,
+ '-output="%s"' % outputfile,
+ '', '']))
+
+ cmdlines = list(cmdgen)
+ self.assertEqual(cmdlines, exp)
+
+ def test_cmdline_generator_multiple_inputs(self):
+ """Tests the cmdline_generator for multiple input support"""
+ paths_to_inputs = ['/some/dir/a', '/some/dir/b']
+ cmdgen = cmdline_generator(self.param_iter,
+ PathToBin=self.abs_path_to_bin,
+ PathToCmd=self.abs_path_to_cmd,
+ PathsToInputs=paths_to_inputs,
+ PathToOutput=self.abs_path_to_output,
+ PathToStdout=self.abs_path_to_stdout,
+ PathToStderr=self.abs_path_to_stderr,
+ UniqueOutputs=False,
+ InputParam='-input',
+ OutputParam='-output')
+ bin = self.abs_path_to_bin
+ cmd = self.abs_path_to_cmd
+ inputfile1 = paths_to_inputs[0]
+ inputfile2 = paths_to_inputs[1]
+ outputfile = self.abs_path_to_output
+ stdout = self.abs_path_to_stdout
+ stderr = self.abs_path_to_stderr
+
+ exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1', '-input="%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr])]
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1', '-input="%s"' % inputfile2,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-input="%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-input="%s"' % inputfile2,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '-input="%s"' % inputfile1,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '-input="%s"' % inputfile2,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-input="%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-input="%s"' % inputfile2,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+
+ cmdlines = list(cmdgen)
+ self.assertEqual(cmdlines, exp)
+
+ def test_cmdline_generator_multiple_input_stdin(self):
+ """Tests cmdline_generator for multiple inputs over stdin"""
+ paths_to_inputs = ['/some/dir/a', '/some/dir/b']
+ cmdgen = cmdline_generator(self.param_iter,
+ PathToBin=self.abs_path_to_bin,
+ PathToCmd=self.abs_path_to_cmd,
+ PathsToInputs=paths_to_inputs,
+ PathToOutput=self.abs_path_to_output,
+ PathToStdout=self.abs_path_to_stdout,
+ PathToStderr=self.abs_path_to_stderr,
+ UniqueOutputs=False,
+ InputParam=None,
+ OutputParam='-output')
+ bin = self.abs_path_to_bin
+ cmd = self.abs_path_to_cmd
+ inputfile1 = paths_to_inputs[0]
+ inputfile2 = paths_to_inputs[1]
+ outputfile = self.abs_path_to_output
+ stdout = self.abs_path_to_stdout
+ stderr = self.abs_path_to_stderr
+
+ exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1', '< "%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr])]
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '-flag1', '< "%s"' % inputfile2,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '< "%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1',
+ '< "%s"' % inputfile2,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '< "%s"' % inputfile1,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '-flag1', '< "%s"' % inputfile2,
+ '-output="%s"' % outputfile, '> "%s"' % stdout,
+ '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '< "%s"' % inputfile1,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+ exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2',
+ '< "%s"' % inputfile2,
+ '-output="%s"' % outputfile,
+ '> "%s"' % stdout, '2> "%s"' % stderr]))
+
+ cmdlines = list(cmdgen)
+ self.assertEqual(cmdlines, exp)
+
+ def test_cmdline_generator_missing_input_output_paths(self):
+ # missing input
+ with self.assertRaises(ValueError):
+ list(cmdline_generator(None, PathsToInputs=None))
+ with self.assertRaises(ValueError):
+ list(cmdline_generator(None, PathsToInputs=''))
+ with self.assertRaises(ValueError):
+ list(cmdline_generator(None, PathsToInputs=[]))
+
+ # missing output
+ with self.assertRaises(ValueError):
+ list(cmdline_generator(None, PathsToInputs=['/foo/bar/baz'],
+ PathToOutput=None))
+ with self.assertRaises(ValueError):
+ list(cmdline_generator(None, PathsToInputs=['/foo/bar/baz'],
+ PathToOutput=''))
+
+
+class CommandLineApplicationTests(TestCase):
+
+ """Tests for the CommandLineApplication class"""
+
+ def setUp(self):
+ """setUp for all CommandLineApplication tests"""
+
+ f = open('/tmp/CLAppTester.py', 'w')
+ f.write(script)
+ f.close()
+ system('chmod 777 /tmp/CLAppTester.py')
+
+ # create a copy of the script with a space in the name
+ f = open('/tmp/CLApp Tester.py', 'w')
+ f.write(script)
+ f.close()
+ system('chmod 777 "/tmp/CLApp Tester.py"')
+
+ self.app_no_params = CLAppTester()
+ self.app_no_params_no_stderr = CLAppTester(SuppressStderr=True)
+ self.app_params = CLAppTester({'-F': 'p_file.txt'})
+ self.app_params_space_in_command =\
+ CLAppTester_space_in_command({'-F': 'p_file.txt'})
+ self.app_params_no_stderr = CLAppTester({'-F': 'p_file.txt'},
+ SuppressStderr=True)
+ self.app_params_no_stdout = CLAppTester({'-F': 'p_file.txt'},
+ SuppressStdout=True)
+ self.app_params_input_as_file = \
+ CLAppTester({'-F': 'p_file.txt'},
+ InputHandler='_input_as_lines')
+ self.app_params_WorkingDir = CLAppTester({'-F': 'p_file.txt'},
+ WorkingDir='/tmp/test')
+ self.app_params_WorkingDir_w_space = \
+ CLAppTester({'-F': 'p_file.txt'},
+ WorkingDir='/tmp/test space')
+ self.app_params_TmpDir = CLAppTester({'-F': 'p_file.txt'},
+ TmpDir='/tmp/tmp2')
+ self.app_params_TmpDir_w_space = \
+ CLAppTester({'-F': 'p_file.txt'},
+ TmpDir='/tmp/tmp space')
+ self.data = 42
+
+ def test_base_command(self):
+ """CLAppTester: BaseCommand correctly composed """
+ # No parameters on
+ app = CLAppTester()
+ self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py')
+ # ValuedParameter on/off
+ app.Parameters['-F'].on('junk.txt')
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt"')
+ app.Parameters['-F'].off()
+ self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py')
+ # ValuedParameter accessed by synonym turned on/off
+ app.Parameters['File'].on('junk.txt')
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt"')
+ app.Parameters['File'].off()
+ self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py')
+ # Try multiple parameters, must check for a few different options
+ # because parameters are printed in arbitrary order
+ app.Parameters['-F'].on('junk.txt')
+ app.Parameters['--duh'].on()
+ self.assertTrue(
+ app.BaseCommand ==
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt" --duh' or
+ app.BaseCommand ==
+ 'cd "/tmp/"; /tmp/CLAppTester.py --duh -F "junk.txt"')
+ # Space in _command
+ app = CLAppTester_space_in_command()
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; "/tmp/CLApp Tester.py"')
+
+ def test_getHelp(self):
+ """CLAppTester: getHelp() functions as expected """
+ app = CLAppTester()
+ self.assertEqual(app.getHelp(), 'Duh')
+
+ def test_handle_app_result_build_failure(self):
+ """_handle_app_result_build_failure on CommandLineAppResult() failure
+ """
+ app = CLAppTester_bad_fixed_file()
+ self.assertRaises(ApplicationError, app)
+
+ app = CLAppTester_bad_fixed_file_w_handler()
+ self.assertEqual(app(),
+ "Called self._handle_app_result_build_failure")
+
+ def test_error_on_missing_executable(self):
+ """CLAppTester: Useful error message on executable not found
+ """
+
+ # fake command via self._command
+ class Blah(CLAppTester):
+ _command = 'fake_command_jasdlkfsadlkfskladfkladf'
+
+ self.assertRaises(ApplicationNotFoundError, Blah)
+
+ # real command but bad path via self._command
+ class Blah(CLAppTester):
+ _command = '/not/a/real/path/ls'
+
+ self.assertRaises(ApplicationNotFoundError, Blah)
+
+ # alt _error_on_missing_command function works as expected
+ class Blah(CLAppTester):
+ _command = 'ls'
+
+ def _error_on_missing_application(self, data):
+ raise ApplicationNotFoundError
+
+ self.assertRaises(ApplicationNotFoundError, Blah)
+
+ class Blah(CLAppTester):
+ _command = 'fake_app_asfasdasdasdasdasd'
+
+ def _error_on_missing_application(self, data):
+ pass
+
+ # no error raised
+ Blah()
+
+ def test_no_p_no_d(self):
+ """CLAppTester: parameters turned off, no data"""
+ app = self.app_no_params
+ # test_init
+ assert app.Parameters['-F'].isOff()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py')
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), 'out\n')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'], None)
+ result.cleanUp()
+
+ def test_no_p_data_as_str(self):
+ """CLAppTester: parameters turned off, data as string"""
+ app = self.app_no_params
+ # test_init
+ assert app.Parameters['-F'].isOff()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py')
+ # test_result
+ result = app(self.data)
+ self.assertEqual(result['StdOut'].read(), 'out 43\n')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'], None)
+ result.cleanUp()
+
+ def test_p_data_as_str_suppress_stderr(self):
+ """CLAppTester: parameters turned on, data as string, suppress stderr
+ """
+ app = self.app_params_no_stderr
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app(self.data)
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'], None)
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out 43 p_file.txt')
+ result.cleanUp()
+
+ def test_p_data_as_str_suppress_stdout(self):
+ """CLAppTester: parameters turned on, data as string, suppress stdout
+ """
+ app = self.app_params_no_stdout
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert app.SuppressStdout
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app(self.data)
+ self.assertEqual(result['StdOut'], None)
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out 43 p_file.txt')
+ result.cleanUp()
+
+ def test_p_no_data(self):
+ """CLAppTester: parameters turned on, no data"""
+ app = self.app_params
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_p_space_in_command(self):
+ """CLAppTester: parameters turned on, no data, space in command"""
+ app = self.app_params_space_in_command
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; "/tmp/CLApp Tester.py" -F "p_file.txt"')
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_p_data_as_str(self):
+ """CLAppTester: parameters turned on, data as str"""
+ app = self.app_params
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app(self.data)
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out 43 p_file.txt')
+ result.cleanUp()
+
+ def test_p_data_as_file(self):
+ """CLAppTester: parameters turned on, data as file"""
+ app = self.app_params_input_as_file
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_lines')
+ assert not app.SuppressStderr
+ # test_command
+ # we don't test the command in this case, because we don't know what
+ # the name of the input file is.
+ # test_result
+ result = app([self.data])
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out 43 p_file.txt')
+ result.cleanUp()
+
+ def test_WorkingDir(self):
+ """CLAppTester: WorkingDir functions as expected """
+ system('cp /tmp/CLAppTester.py /tmp/test/CLAppTester.py')
+ app = self.app_params_WorkingDir
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # WorkingDir is what we expect
+ self.assertEqual(app.WorkingDir, '/tmp/test/')
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ ('cd "/tmp/test/"; /tmp/CLAppTester.py '
+ '-F "p_file.txt"'))
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+
+ # Make sure that the parameterized file is in the correct place
+ self.assertEqual(result['parameterized_file'].name,
+ '/tmp/test/p_file.txt')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_WorkingDir_w_space(self):
+ """CLAppTester: WorkingDir w/ space in path functions as expected """
+ system('cp /tmp/CLAppTester.py "/tmp/test space/CLAppTester.py"')
+ app = self.app_params_WorkingDir_w_space
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # WorkingDir is what we expect
+ self.assertEqual(app.WorkingDir, '/tmp/test space/')
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ ('cd "/tmp/test space/"; /tmp/CLAppTester.py'
+ ' -F "p_file.txt"'))
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+
+ # Make sure that the parameterized file is in the correct place
+ self.assertEqual(result['parameterized_file'].name,
+ '/tmp/test space/p_file.txt')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_TmpDir(self):
+ """CLAppTester: Alternative TmpDir functions as expected"""
+ app = self.app_params_TmpDir
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # TmpDir is what we expect
+ self.assertEqual(app.TmpDir, '/tmp/tmp2/')
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+
+ # Make sure that the parameterized file is in the correct place
+ self.assertEqual(result['parameterized_file'].name,
+ '/tmp/p_file.txt')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_TmpDir_w_space(self):
+ """CLAppTester: TmpDir functions as expected w space in name"""
+ app = self.app_params_TmpDir_w_space
+ # test_init
+ assert app.Parameters['-F'].isOn()
+ self.assertEqual(app.InputHandler, '_input_as_string')
+ assert not app.SuppressStderr
+ # TmpDir is what we expect
+ self.assertEqual(app.TmpDir, '/tmp/tmp space/')
+ # test_command
+ self.assertEqual(app.BaseCommand,
+ 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"')
+ # test_result
+ result = app()
+ self.assertEqual(result['StdOut'].read(), '')
+ self.assertEqual(result['StdErr'].read(), 'I am stderr\n')
+ self.assertEqual(result['ExitStatus'], 0)
+ self.assertEqual(result['fixed_file'].read(), 'I am fixed file')
+ self.assertEqual(result['base_dep_1'].read(), 'base dependent 1')
+ self.assertEqual(result['base_dep_2'].read(), 'base dependent 2')
+
+ # Make sure that the parameterized file is in the correct place
+ self.assertEqual(result['parameterized_file'].name,
+ '/tmp/p_file.txt')
+ self.assertEqual(result['parameterized_file'].read(),
+ 'out p_file.txt')
+ result.cleanUp()
+
+ def test_input_as_string(self):
+ """CLAppTester: _input_as_string functions as expected """
+ self.assertEqual(self.app_no_params._input_as_string('abcd'), 'abcd')
+ self.assertEqual(self.app_no_params._input_as_string(42), '42')
+ self.assertEqual(self.app_no_params._input_as_string(None), 'None')
+ self.assertEqual(self.app_no_params._input_as_string([1]), '[1]')
+ self.assertEqual(self.app_no_params._input_as_string({'a': 1}),
+ "{'a': 1}")
+
+ def test_input_as_lines_from_string(self):
+ """CLAppTester: _input_as_lines functions as expected w/ data as str
+ """
+ filename = self.app_no_params._input_as_lines('abcd')
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'a\n')
+ self.assertEqual(f.readline(), 'b\n')
+ self.assertEqual(f.readline(), 'c\n')
+ self.assertEqual(f.readline(), 'd')
+ f.close()
+ remove(filename)
+
+ def test_input_as_lines_from_list(self):
+ """CLAppTester: _input_as_lines functions as expected w/ data as list
+ """
+ filename = self.app_no_params._input_as_lines(['line 1', None, 3])
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'line 1\n')
+ self.assertEqual(f.readline(), 'None\n')
+ self.assertEqual(f.readline(), '3')
+ f.close()
+ remove(filename)
+
+ def test_input_as_lines_from_list_w_newlines(self):
+ """CLAppTester: _input_as_lines functions w/ data as list w/ newlines
+ """
+ filename = self.app_no_params._input_as_lines(['line 1\n', None, 3])
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'line 1\n')
+ self.assertEqual(f.readline(), 'None\n')
+ self.assertEqual(f.readline(), '3')
+ f.close()
+ remove(filename)
+
+ def test_input_as_multiline_string(self):
+ """CLAppTester: _input_as_multiline_string functions as expected
+ """
+ filename = self.app_no_params._input_as_multiline_string(
+ 'line 1\nNone\n3')
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'line 1\n')
+ self.assertEqual(f.readline(), 'None\n')
+ self.assertEqual(f.readline(), '3')
+ f.close()
+ remove(filename)
+
+ def test_input_as_lines_from_list_single_entry(self):
+ """CLAppTester: _input_as_lines functions as expected w/ 1 element list
+ """
+ filename = self.app_no_params._input_as_lines(['line 1'])
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'line 1')
+ f.close()
+ remove(filename)
+
+ def test_input_as_multiline_string_single_line(self):
+ """CLAppTester: _input_as_multiline_string functions w/ single line
+ """
+ # functions as expected with single line string
+ filename = self.app_no_params._input_as_multiline_string(
+ 'line 1')
+ self.assertEqual(filename[0], '/')
+ f = open(filename)
+ self.assertEqual(f.readline(), 'line 1')
+ f.close()
+ remove(filename)
+
+ def test_getTmpFilename_non_default(self):
+ """TmpFilename handles alt tmp_dir, prefix and suffix properly"""
+ app = CLAppTester()
+ obs = app.getTmpFilename(include_class_id=False)
+ self.assertTrue(obs.startswith(app.TmpDir + 'tmp'))
+ self.assertTrue(obs.endswith('.txt'))
+
+ obs = app.getTmpFilename(tmp_dir="/tmp/blah", prefix="app_ctl_test",
+ suffix='.test', include_class_id=False)
+ self.assertTrue(obs.startswith('/tmp/blah/app_ctl_test'))
+ self.assertTrue(obs.endswith('.test'))
+
+ def test_getTmpFilename_defaults_to_no_class_id(self):
+ """CLAppTester: getTmpFilename doesn't include class id by default
+ """
+ # I want to explicitly test for this so people don't forget to
+ # set the default to False if they change it for testing purposes
+ app = CLAppTester()
+ self.assertFalse(app.getTmpFilename().
+ startswith(app.TmpDir + 'tmpCLAppTester'))
+ self.assertTrue(app.getTmpFilename(include_class_id=True).
+ startswith(app.TmpDir + 'tmpCLAppTester'))
+
+ def test_input_as_path(self):
+ """CLAppTester: _input_as_path casts data to FilePath"""
+ actual = self.app_no_params._input_as_path('test.pdb')
+ self.assertEqual(actual, 'test.pdb')
+ self.assertEqual(str(actual), '"test.pdb"')
+ actual = self.app_no_params._input_as_path('te st.pdb')
+ self.assertEqual(actual, 'te st.pdb')
+ self.assertEqual(str(actual), '"te st.pdb"')
+ actual = self.app_no_params._input_as_path('./test.pdb')
+ self.assertEqual(actual, './test.pdb')
+ self.assertEqual(str(actual), '"./test.pdb"')
+ actual = self.app_no_params._input_as_path('/this/is/a/test.pdb')
+ self.assertEqual(actual, '/this/is/a/test.pdb')
+ self.assertEqual(str(actual), '"/this/is/a/test.pdb"')
+ actual = self.app_no_params._input_as_path('/this/i s/a/test.pdb')
+ self.assertEqual(actual, '/this/i s/a/test.pdb')
+ self.assertEqual(str(actual), '"/this/i s/a/test.pdb"')
+
+ def test_input_as_paths(self):
+ """CLAppTester: _input_as_paths casts each input to FilePath """
+ input = ['test.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"test.pdb"'
+ self.assertEqual(actual, expected)
+
+ input = ['test1.pdb', 'test2.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"test1.pdb" "test2.pdb"'
+ self.assertEqual(actual, expected)
+
+ input = ['/path/to/test1.pdb', 'test2.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"/path/to/test1.pdb" "test2.pdb"'
+ self.assertEqual(actual, expected)
+
+ input = ['test1.pdb', '/path/to/test2.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"test1.pdb" "/path/to/test2.pdb"'
+ self.assertEqual(actual, expected)
+
+ input = ['/path/to/test1.pdb', '/path/to/test2.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"/path/to/test1.pdb" "/path/to/test2.pdb"'
+ self.assertEqual(actual, expected)
+
+ input = ['/pa th/to/test1.pdb', '/path/to/te st2.pdb']
+ actual = self.app_no_params._input_as_paths(input)
+ expected = '"/pa th/to/test1.pdb" "/path/to/te st2.pdb"'
+ self.assertEqual(actual, expected)
+
+ def test_absolute(self):
+ """CLAppTester: _absolute converts relative paths to absolute paths
+ """
+ absolute = self.app_no_params._absolute
+ self.assertEqual(absolute('/tmp/test.pdb'), '/tmp/test.pdb')
+ self.assertEqual(absolute('test.pdb'), '/tmp/test.pdb')
+
+ def test_working_dir_setting(self):
+ """CLAppTester: WorkingDir is set correctly """
+ app = CLAppTester_no_working_dir()
+ self.assertEqual(app.WorkingDir, getcwd() + '/')
+
+ def test_error_raised_on_command_None(self):
+ """CLAppTester: An error is raises when _command == None """
+ app = CLAppTester()
+ app._command = None
+ self.assertRaises(ApplicationError, app._get_base_command)
+
+ def test_rejected_exit_status(self):
+ """CLAppTester_reject_exit_status results in useful error """
+ app = CLAppTester_reject_exit_status()
+ self.assertRaises(ApplicationError, app)
+
+ def test_getTmpFilename(self):
+ """TmpFilename should return filename of correct length"""
+ app = CLAppTester()
+ obs = app.getTmpFilename(include_class_id=True)
+ # leaving the strings in this statement so it's clear where the
+ # expected length comes from
+ self.assertEqual(len(obs), len(app.TmpDir) + app.TmpNameLen +
+ len('tmp') + len('CLAppTester') + len('.txt'))
+ assert obs.startswith(app.TmpDir)
+ chars = set(obs[18:])
+ assert len(chars) > 1
+
+ obs = app.getTmpFilename(include_class_id=False)
+ # leaving the strings in this statement so it's clear where the
+ # expected length comes from
+ self.assertEqual(len(obs), len(app.TmpDir) + app.TmpNameLen +
+ len('tmp') + len('.txt'))
+ assert obs.startswith(app.TmpDir)
+
+ def test_getTmpFilename_prefix_suffix_result_constructor(self):
+ """TmpFilename: result has correct prefix, suffix, type"""
+ app = CLAppTester()
+ obs = app.getTmpFilename(prefix='blah', include_class_id=False)
+ self.assertTrue(obs.startswith(app.TmpDir + 'blah'))
+ obs = app.getTmpFilename(suffix='.blah', include_class_id=False)
+ self.assertTrue(obs.endswith('.blah'))
+ # Prefix defaults to not include the class name
+ obs = app.getTmpFilename(include_class_id=False)
+ self.assertFalse(obs.startswith(app.TmpDir + 'tmpCLAppTester'))
+ self.assertTrue(obs.endswith('.txt'))
+ # including class id functions correctly
+ obs = app.getTmpFilename(include_class_id=True)
+ self.assertTrue(obs.startswith(app.TmpDir + 'tmpCLAppTester'))
+ self.assertTrue(obs.endswith('.txt'))
+
+ # result as FilePath
+ obs = app.getTmpFilename(result_constructor=FilePath)
+ self.assertEqual(type(obs), FilePath)
+
+ # result as str (must check that result is a str and is not a FilePath
+ # since a FilePath is a str)
+ obs = app.getTmpFilename(result_constructor=str)
+ self.assertEqual(type(obs), str)
+ self.assertNotEqual(type(obs), FilePath)
+
+
+class ConvenienceFunctionTests(TestCase):
+
+ """
+ """
+
+ def setUp(self):
+ """
+ """
+ self.tmp_dir = gettempdir()
+ self.tmp_name_len = 20
+
+ def test_guess_input_handler(self):
+ """guess_input_handler should correctly identify input"""
+ gih = guess_input_handler
+ self.assertEqual(gih('abc.txt'), '_input_as_string')
+ self.assertEqual(gih('>ab\nTCAG'), '_input_as_multiline_string')
+ self.assertEqual(gih(['ACC', 'TGA'], True), '_input_as_seqs')
+ self.assertEqual(gih(['>a', 'ACC', '>b', 'TGA']), '_input_as_lines')
+ self.assertEqual(gih([('a', 'ACC'), ('b', 'TGA')]),
+ '_input_as_seq_id_seq_pairs')
+ self.assertEqual(gih([]), '_input_as_lines')
+
+ def test_get_tmp_filename(self):
+ """get_tmp_filename should return filename of correct length
+
+ Adapted from the CommandLineApplication tests of the member
+ function
+
+ """
+ obs = get_tmp_filename()
+ # leaving the strings in this statement so it's clear where the
+ # expected length comes from
+ self.assertEqual(len(obs),
+ len(self.tmp_dir) + len('/') + self.tmp_name_len +
+ len('tmp') + len('.txt'))
+ self.assertTrue(obs.startswith(self.tmp_dir))
+
+ # different results on different calls
+ self.assertNotEqual(get_tmp_filename(), get_tmp_filename())
+
+ obs = get_tmp_filename()
+ # leaving the strings in this statement so it's clear where the
+ # expected length comes from
+ self.assertEqual(len(obs),
+ len(self.tmp_dir) + len('/') + self.tmp_name_len +
+ len('tmp') + len('.txt'))
+ assert obs.startswith(self.tmp_dir)
+
+ def test_get_tmp_filename_prefix_suffix_constructor(self):
+ """get_tmp_filename: result has correct prefix, suffix, type
+
+ Adapted from the CommandLineApplication tests of the member
+ function
+ """
+ obs = get_tmp_filename(prefix='blah')
+ self.assertTrue(obs.startswith('%s/blah' % self.tmp_dir))
+ obs = get_tmp_filename(suffix='.blah')
+ self.assertTrue(obs.endswith('.blah'))
+
+ # result as FilePath
+ obs = get_tmp_filename(result_constructor=FilePath)
+ self.assertEqual(type(obs), FilePath)
+
+ # result as str (must check that result is a str and is not a FilePath
+ # since a FilePath is a str)
+ obs = get_tmp_filename(result_constructor=str)
+ self.assertEqual(type(obs), str)
+ self.assertNotEqual(type(obs), FilePath)
+
+ def test_which_found(self):
+ """Test finding filepath for executable that can be found."""
+ obs = which('ls')
+ self.assertTrue(obs is not None)
+ self.assertTrue(exists(obs))
+
+ def test_which_not_found(self):
+ """Test finding filepath for executable that cannot be found."""
+ obs = which('thiscommandhadbetternotexist')
+ self.assertTrue(obs is None)
+
+ def test_which_env_var(self):
+ """Test finding filepath using an env_var other than the default."""
+ obs = which('ls', env_var='THISENVVARHADBETTERNOTEXIST')
+ self.assertTrue(obs is None)
+
+
+def teardown_module():
+ """This will remove the test script."""
+ for dir, n, fnames in walk('/tmp/test/'):
+ for f in fnames:
+ try:
+ remove(dir + f)
+ except OSError:
+ pass
+
+ remove('/tmp/CLAppTester.py')
+ remove('/tmp/test space/CLAppTester.py')
+ remove('/tmp/CLApp Tester.py')
+ rmdir('/tmp/tmp space')
+ rmdir('/tmp/test')
+ rmdir('/tmp/test space')
+ rmdir('/tmp/tmp2')
+ rmdir('/tmp/blah')
+
+
+# =====================END OF TESTS==================================
+
+script = """#!/usr/bin/env python
+#This is a test script intended to test the CommandLineApplication
+#class and CommandLineAppResult class
+
+from __future__ import absolute_import, division, print_function
+
+from sys import argv, stderr, stdin
+from os import isatty
+
+out_file_name = None
+
+input_arg = None
+
+# parse input
+try:
+ if argv[1] == '-F':
+ out_file_name = argv[2]
+except IndexError:
+ pass
+try:
+ if out_file_name:
+ input_arg = argv[3]
+ else:
+ input_arg = argv[1]
+except IndexError:
+ pass
+# Create the output string
+out = 'out'
+# get input
+try:
+ f = open(str(input_arg))
+ data = int(f.readline().strip())
+except IOError:
+ try:
+ data = int(input_arg)
+ except TypeError:
+ data = None
+
+if data:
+ data = str(data + 1)
+ out = ' '.join([out,data])
+
+# Write base dependent output files
+base = 'BASE'
+f = open('/tmp/' + base + '.1','w')
+f.writelines(['base dependent 1'])
+f.close()
+f = open('/tmp/' + base + '.2','w')
+f.writelines(['base dependent 2'])
+f.close()
+
+# If output to file, open the file and write output to it
+if out_file_name:
+ filename = argv[2]
+ f = open(''.join([out_file_name]),'w')
+ out = ' '.join([out,out_file_name])
+ f.writelines(out)
+ f.close()
+else:
+ print(out)
+
+#generate some stderr
+print('I am stderr', file=stderr)
+
+# Write the fixed file
+f = open('/tmp/fixed.txt','w')
+f.writelines(['I am fixed file'])
+f.close()
+
+"""
+
+
+class CLAppTester(CommandLineApplication):
+ _parameters = {
+ '-F': ValuedParameter(Prefix='-', Name='F', Delimiter=' ',
+ Value=None, Quote="\""),
+ '--duh': FlagParameter(Prefix='--', Name='duh')}
+ _command = '/tmp/CLAppTester.py'
+ _synonyms = {'File': '-F', 'file': '-F'}
+ _working_dir = '/tmp'
+
+ def _get_result_paths(self, data):
+
+ if self.Parameters['-F'].isOn():
+ param_path = ''.join(
+ [self.WorkingDir, self.Parameters['-F'].Value])
+ else:
+ param_path = None
+
+ result = {}
+ result['fixed_file'] = ResultPath(Path='/tmp/fixed.txt')
+ result['parameterized_file'] = \
+ ResultPath(Path=param_path,
+ IsWritten=self.Parameters['-F'].isOn())
+ result['base_dep_1'] = ResultPath(Path=self._build_name(suffix='.1'))
+ result['base_dep_2'] = ResultPath(Path=self._build_name(suffix='.2'))
+ return result
+
+ def _build_name(self, suffix):
+ return '/tmp/BASE' + suffix
+
+ def getHelp(self):
+ return """Duh"""
+
+
+class CLAppTester_no_working_dir(CLAppTester):
+ _working_dir = None
+
+
+class CLAppTester_reject_exit_status(CLAppTester):
+
+ def _accept_exit_status(self, exit_status):
+ return False
+
+
+class CLAppTester_bad_fixed_file(CLAppTester):
+
+ def _get_result_paths(self, data):
+
+ if self.Parameters['-F'].isOn():
+ param_path = ''.join(
+ [self.WorkingDir, self.Parameters['-F'].Value])
+ else:
+ param_path = None
+
+ result = {}
+ result['fixed_file'] = ResultPath(Path='/tmp/fixed.txt')
+ result['fixed_file_bad'] = ResultPath(Path='/tmp/i_dont_exist.txt')
+ result['parameterized_file'] = \
+ ResultPath(Path=param_path,
+ IsWritten=self.Parameters['-F'].isOn())
+ result['base_dep_1'] = ResultPath(Path=self._build_name(suffix='.1'))
+ result['base_dep_2'] = ResultPath(Path=self._build_name(suffix='.2'))
+ return result
+
+
+class CLAppTester_bad_fixed_file_w_handler(CLAppTester_bad_fixed_file):
+
+ def _handle_app_result_build_failure(
+ self,
+ out,
+ err,
+ exit_status,
+ result_paths):
+ return "Called self._handle_app_result_build_failure"
+
+
+class CLAppTester_space_in_command(CLAppTester):
+ _command = '"/tmp/CLApp Tester.py"'
+
+
+class ParameterCombinationsApp(CommandLineApplication):
+
+ """ParameterCombinations mock application to wrap"""
+ _command = 'testcmd'
+ _parameters = {'-flag1': FlagParameter(Prefix='-', Name='flag1'),
+ '-flag2': FlagParameter(Prefix='-', Name='flag2'),
+ '--value1': ValuedParameter(Prefix='--', Name='value1'),
+ '-value2': ValuedParameter(Prefix='-', Name='value2'),
+ '-mix1': MixedParameter(Prefix='-', Name='mix1'),
+ '-mix2': MixedParameter(Prefix='-', Name='mix2'),
+ '-delim': ValuedParameter(Prefix='-', Name='delim',
+ Delimiter='aaa'),
+ '-default': ValuedParameter(Prefix='-', Name='default',
+ Value=42, Delimiter='='),
+ '-input': ValuedParameter(Prefix='-', Name='input',
+ Delimiter='='),
+ '-output': ValuedParameter(Prefix='-', Name='output',
+ Delimiter='=')}
+if __name__ == '__main__':
+ from nose import runmodule
+ runmodule()
diff --git a/burrito/util.py b/burrito/util.py
new file mode 100644
index 0000000..1116ed3
--- /dev/null
+++ b/burrito/util.py
@@ -0,0 +1,791 @@
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+from future.utils import implements_iterator
+
+import os
+from os import remove, system, mkdir, getcwd
+from os.path import isabs, exists
+from random import choice
+from tempfile import gettempdir
+from copy import deepcopy
+from itertools import product
+
+from burrito.parameters import Parameters, FilePath
+
+# the following are used to create temp file names
+from string import ascii_letters, digits
+_all_chars = ascii_letters + digits
+
+
+def which(executable_name, env_var='PATH'):
+ """Equivalent to ``which executable_name`` in a *nix environment.
+
+ Will return ``None`` if ``executable_name`` cannot be found in ``env_var``
+ or if ``env_var`` is not set. Otherwise will return the first match in
+ ``env_var``.
+
+ Note: this function will likely not work on Windows.
+
+ Code taken and modified from:
+ http://www.velocityreviews.com/forums/
+ t689526-python-library-call-equivalent-to-which-command.html
+
+ """
+ exec_fp = None
+
+ if env_var in os.environ:
+ paths = os.environ[env_var]
+
+ for path in paths.split(os.pathsep):
+ curr_exec_fp = os.path.join(path, executable_name)
+
+ if os.access(curr_exec_fp, os.X_OK):
+ exec_fp = curr_exec_fp
+ break
+
+ return exec_fp
+
+
+class ApplicationError(OSError):
+ pass
+
+
+class ApplicationNotFoundError(ApplicationError):
+ pass
+
+
+class ResultPath(object):
+
+ """ Hold a file path a boolean value specifying whether file was written
+ """
+
+ def __init__(self, Path, IsWritten=True):
+ """ Initialize the ResultPath object
+
+ Path: a string representing the absolute or relative path where
+ the file can be found
+ IsWritten: a boolean specifying whether the file has been written,
+ default = True
+ """
+ self.Path = FilePath(Path)
+ self.IsWritten = IsWritten
+
+
+class CommandLineAppResult(dict):
+
+ """ Class for holding the result of a CommandLineApplication run """
+
+ def __init__(self, out, err, exit_status, result_paths):
+ """Initialization of CommandLineAppResult
+
+ out: a file handler to the file containing the stdout
+ err: a file handler to the file containing the stderr
+ exit_status: the exit status of the program, 0 if run ok, 1 else.
+ result_paths: dictionary containing ResultPath objects for each
+ output file that could be written
+ """
+
+ self['StdOut'] = out
+ self['StdErr'] = err
+ self['ExitStatus'] = exit_status
+
+ self.file_keys = result_paths.keys()
+ for key, value in result_paths.items():
+ if value.IsWritten:
+ try:
+ self[key] = open(value.Path)
+ except IOError:
+ raise ApplicationError('Could not open %s' % value.Path)
+ else:
+ self[key] = None
+
+ def cleanUp(self):
+ """ Delete files that are written by CommandLineApplication from disk
+
+ WARNING: after cleanUp() you may still have access to part of
+ your result data, but you should be aware that if the file
+ size exceeds the size of the buffer you will only have part
+ of the file. To be safe, you should not use cleanUp() until
+ you are done with the file or have copied it to a different
+ location.
+ """
+ file_keys = self.file_keys
+ for item in file_keys:
+ if self[item] is not None:
+ self[item].close()
+ remove(self[item].name)
+
+ # remove input handler temp files
+ if hasattr(self, "_input_filename"):
+ remove(self._input_filename)
+
+ def __del__(self):
+ """ Delete temporary files created by the CommandLineApplication
+ """
+ if self['StdOut'] is not None:
+ remove(self['StdOut'].name)
+ if self['StdErr'] is not None:
+ remove(self['StdErr'].name)
+
+
+class Application(object):
+
+ """ Generic Class for controlling an application """
+
+ _command = None
+ _command_delimiter = ' '
+ _parameters = {}
+ _synonyms = {}
+
+ def __init__(self, params=None):
+ """
+ params: a dict of parameters which should be turned on where the
+ key is either the parameter id or a synonym for the parameter
+ and the value is either the value for the parameter or None
+ """
+ self.Parameters = Parameters(self._parameters, self._synonyms)
+ if params:
+ for key, v in params.items():
+ try:
+ self.Parameters[key].on(v)
+ except TypeError:
+ self.Parameters[key].on()
+
+
+class CommandLineApplication(Application):
+
+ """ Generic class for controlling command line applications
+ """
+
+ _input_handler = '_input_as_string'
+ _suppress_stderr = False
+ _suppress_stdout = False
+ _working_dir = None
+
+ def __init__(self, params=None, InputHandler=None, SuppressStderr=None,
+ SuppressStdout=None, WorkingDir=None, TmpDir=gettempdir(),
+ TmpNameLen=20, HALT_EXEC=False):
+ """ Initialize the CommandLineApplication object
+
+ params: a dictionary mapping the Parameter id or synonym to its
+ value (or None for FlagParameters or MixedParameters in flag
+ mode) for Parameters that should be turned on
+ InputHandler: this is the method to be run on data when it is
+ passed into call. This should be a string containing the
+ method name. The default is _input_as_string which casts data
+ to a string before appending it to the command line argument
+ SuppressStderr: if set to True, will route standard error to
+ /dev/null, False by default
+ SuppressStdout: if set to True, will route standard out to
+ /dev/null, False by default
+ WorkingDir: the directory where you want the application to run,
+ default is the current working directory, but is useful to
+ change in cases where the program being run creates output
+ to its current working directory and you either don't want
+ it to end up where you are running the program, or the user
+ running the script doesn't have write access to the current
+ working directory
+ WARNING: WorkingDir MUST be an absolute path!
+ TmpDir: the directory where temp files will be created
+ TmpNameLen: the length of the temp file name
+ HALT_EXEC: if True, raises exception w/ command output just
+ before execution, doesn't clean up temp files. Default False.
+ """
+ # Determine if the application is installed, and raise an error if not
+ self._error_on_missing_application(params)
+
+ # set attributes to parameter that was passed in or class default
+ if InputHandler is not None:
+ self.InputHandler = InputHandler
+ else:
+ self.InputHandler = self._input_handler
+ if SuppressStderr is not None:
+ self.SuppressStderr = SuppressStderr
+ else:
+ self.SuppressStderr = self._suppress_stderr
+ if SuppressStdout is not None:
+ self.SuppressStdout = SuppressStdout
+ else:
+ self.SuppressStdout = self._suppress_stdout
+ if WorkingDir is not None:
+ working_dir = WorkingDir
+ else:
+ working_dir = self._working_dir or getcwd()
+ self.WorkingDir = FilePath(working_dir)
+ if not TmpDir.endswith("/"):
+ TmpDir += "/"
+ self.TmpDir = FilePath(TmpDir)
+ self.TmpNameLen = TmpNameLen
+ self.HaltExec = HALT_EXEC
+
+ # create a variable to hold the name of the file being used as
+ # input to the application. this is important especially when
+ # you are using an input handler which creates a temporary file
+ # and the output filenames are based on the input filenames
+ self._input_filename = None
+
+ super(CommandLineApplication, self).__init__(params=params)
+
+ def __call__(self, data=None, remove_tmp=True):
+ """Run the application with the specified kwargs on data
+
+ data: anything that can be cast into a string or written out to
+ a file. Usually either a list of things or a single string or
+ number. input_handler will be called on this data before it
+ is passed as part of the command-line argument, so by creating
+ your own input handlers you can customize what kind of data
+ you want your application to accept
+
+ remove_tmp: if True, removes tmp files
+ """
+ input_handler = self.InputHandler
+ suppress_stdout = self.SuppressStdout
+ suppress_stderr = self.SuppressStderr
+ if suppress_stdout:
+ outfile = FilePath('/dev/null')
+ else:
+ outfile = self.getTmpFilename(self.TmpDir)
+ if suppress_stderr:
+ errfile = FilePath('/dev/null')
+ else:
+ errfile = FilePath(self.getTmpFilename(self.TmpDir))
+ if data is None:
+ input_arg = ''
+ else:
+ input_arg = getattr(self, input_handler)(data)
+
+ # Build up the command, consisting of a BaseCommand followed by
+ # input and output (file) specifications
+ command = self._command_delimiter.join(filter(None,
+ [self.BaseCommand,
+ str(input_arg),
+ '>', str(outfile),
+ '2>', str(errfile)]))
+ if self.HaltExec:
+ raise AssertionError("Halted exec with command:\n" + command)
+ # The return value of system is a 16-bit number containing the signal
+ # number that killed the process, and then the exit status.
+ # We only want to keep the exit status so do a right bitwise shift to
+ # get rid of the signal number byte
+ exit_status = system(command) >> 8
+
+ # Determine if error should be raised due to exit status of
+ # appliciation
+ if not self._accept_exit_status(exit_status):
+ raise ApplicationError('Unacceptable application exit ' +
+ 'status: %s\n' % str(exit_status) +
+ 'Command:\n%s\n' % command +
+ 'StdOut:\n%s\n' % open(outfile).read() +
+ 'StdErr:\n%s\n' % open(errfile).read())
+
+ # open the stdout and stderr if not being suppressed
+ out = None
+ if not suppress_stdout:
+ out = open(outfile, "r")
+ err = None
+ if not suppress_stderr:
+ err = open(errfile, "r")
+
+ result_paths = self._get_result_paths(data)
+ try:
+ result = \
+ CommandLineAppResult(out, err, exit_status,
+ result_paths=result_paths)
+ except ApplicationError:
+ result = \
+ self._handle_app_result_build_failure(out, err, exit_status,
+ result_paths)
+
+ # Clean up the input file if one was created
+ if remove_tmp:
+ if self._input_filename:
+ remove(self._input_filename)
+ self._input_filename = None
+
+ return result
+
+ def _handle_app_result_build_failure(
+ self,
+ out,
+ err,
+ exit_status,
+ result_paths):
+ """Called if ApplicationError raised on building CommandLineAppResult
+
+ This is useful for checking log files or other special handling
+ in cases when expected files aren't present.
+
+ """
+ raise ApplicationError("Error constructing CommandLineAppResult.")
+
+ def _input_as_string(self, data):
+ """ Return data as a string """
+ return str(data)
+
+ def _input_as_multiline_string(self, data):
+ """Write a multiline string to a temp file and return the filename.
+
+ data: a multiline string to be written to a file.
+
+ * Note: the result will be the filename as a FilePath object
+ (which is a string subclass).
+
+ """
+ filename = self._input_filename = \
+ FilePath(self.getTmpFilename(self.TmpDir))
+ data_file = open(filename, 'w')
+ data_file.write(data)
+ data_file.close()
+ return filename
+
+ def _input_as_lines(self, data):
+ """ Write a seq of lines to a temp file and return the filename string
+
+ data: a sequence to be written to a file, each element of the
+ sequence will compose a line in the file
+ * Note: the result will be the filename as a FilePath object
+ (which is a string subclass).
+
+ * Note: '\n' will be stripped off the end of each sequence element
+ before writing to a file in order to avoid multiple new lines
+ accidentally be written to a file
+ """
+ filename = self._input_filename = \
+ FilePath(self.getTmpFilename(self.TmpDir))
+ filename = FilePath(filename)
+ data_file = open(filename, 'w')
+ data_to_file = '\n'.join([str(d).strip('\n') for d in data])
+ data_file.write(data_to_file)
+ data_file.close()
+ return filename
+
+ def _input_as_path(self, data):
+ """ Return data as string with the path wrapped in quotes
+
+ data: path or filename, most likely as a string
+
+ * Note: the result will be the filename as a FilePath object
+ (which is a string subclass).
+
+ """
+ return FilePath(data)
+
+ def _input_as_paths(self, data):
+ """ Return data as a space delimited string with each path quoted
+
+ data: paths or filenames, most likely as a list of
+ strings
+
+ """
+ return self._command_delimiter.join(
+ map(str, map(self._input_as_path, data)))
+
+ def _absolute(self, path):
+ """ Convert a filename to an absolute path """
+ path = FilePath(path)
+ if isabs(path):
+ return path
+ else:
+ # these are both Path objects, so joining with + is acceptable
+ return self.WorkingDir + path
+
+ def _get_base_command(self):
+ """ Returns the full command string
+
+ input_arg: the argument to the command which represents the input
+ to the program, this will be a string, either
+ representing input or a filename to get input from
+ tI"""
+ command_parts = []
+ # Append a change directory to the beginning of the command to change
+ # to self.WorkingDir before running the command
+ # WorkingDir should be in quotes -- filenames might contain spaces
+ cd_command = ''.join(['cd ', str(self.WorkingDir), ';'])
+ if self._command is None:
+ raise ApplicationError('_command has not been set.')
+ command = self._command
+ parameters = self.Parameters
+
+ command_parts.append(cd_command)
+ command_parts.append(command)
+ command_parts.append(self._command_delimiter.join(filter(
+ None, (map(str, parameters.values())))))
+
+ return self._command_delimiter.join(command_parts).strip()
+
+ BaseCommand = property(_get_base_command)
+
+ def _get_WorkingDir(self):
+ """Gets the working directory"""
+ return self._curr_working_dir
+
+ def _set_WorkingDir(self, path):
+ """Sets the working directory
+
+ Appends a slash to the end of path
+ The reasoning behind this is that the user may or may not pass
+ in a path with a '/' at the end. Since having multiple
+ '/' at the end doesn't hurt anything, it's convienient to
+ be able to rely on it, and not have to check for it
+ """
+ self._curr_working_dir = FilePath(path) + '/'
+ try:
+ mkdir(self.WorkingDir)
+ except OSError:
+ # Directory already exists
+ pass
+
+ WorkingDir = property(_get_WorkingDir, _set_WorkingDir)
+
+ def _error_on_missing_application(self, params):
+ """ Raise an ApplicationNotFoundError if the app is not accessible
+
+ This method checks in the system path (usually $PATH) or for
+ the existence of self._command. If self._command is not found
+ in either place, an ApplicationNotFoundError is raised to
+ inform the user that the application they are trying to access is
+ not available.
+
+ This method should be overwritten when self._command does not
+ represent the relevant executable (e.g., self._command = 'prog -a')
+ or in more complex cases where the file to be executed may be
+ passed as a parameter (e.g., with java jar files, where the
+ jar file is passed to java via '-jar'). It can also be overwritten
+ to by-pass testing for application presence by never raising an
+ error.
+ """
+ command = self._command
+ # strip off " characters, in case we got a FilePath object
+ found_in_path = which(command.strip('"')) is not None
+ if not (exists(command) or found_in_path):
+ raise ApplicationNotFoundError("Cannot find %s. Is it installed? "
+ "Is it in your path?" % command)
+
+ def _accept_exit_status(self, exit_status):
+ """ Return False to raise an error due to exit_status of applciation
+
+ This method should be overwritten if you'd like to raise an error
+ based on certain exit statuses of the application that was run. The
+ default is that no value of exit_status will raise an error.
+ """
+ return True
+
+ def _get_result_paths(self, data):
+ """ Return dict of ResultPath objects representing all possible output
+
+ This method should be overwritten if the application creates
+ output other than stdout and stderr. This dictionary will have
+ keys based on the name that you'd like to access the file by in
+ the CommandLineAppResult object that will be created, and the
+ values which are ResultPath objects. For an example of how this
+ should be written see the rnaview or vienna_package classes.
+ WARNING: be sure that the path that you give a file is accurate
+ from any directory where the program could be running. For
+ that reason, absolute paths are very good. Relative paths
+ can also be used as long as you are careful. For cases where
+ the application leaves files in the current working directory,
+ you should append self.WorkingDir to the beginning of the file
+ name. It would be a very bad idea to just use a file name as
+ the path, in some cases that you might not be testing for.
+ """
+ return {}
+
+ def getTmpFilename(self, tmp_dir=None, prefix='tmp', suffix='.txt',
+ include_class_id=False, result_constructor=FilePath):
+ """ Return a temp filename
+
+ tmp_dir: directory where temporary files will be stored
+ prefix: text to append to start of file name
+ suffix: text to append to end of file name
+ include_class_id: if True, will append a class identifier (built
+ from the class name) to the filename following prefix. This is
+ False by default b/c there is some string processing overhead
+ in getting the class name. This will probably be most useful for
+ testing: if temp files are being left behind by tests, you can
+ turn this on in here (temporarily) to find out which tests are
+ leaving the temp files.
+ result_constructor: the constructor used to build the result
+ (default: cogent.app.parameters.FilePath). Note that joining
+ FilePath objects with one another or with strings, you must use
+ the + operator. If this causes trouble, you can pass str as the
+ the result_constructor.
+ """
+
+ # check not none
+ if not tmp_dir:
+ tmp_dir = self.TmpDir
+ # if not current directory, append "/" if not already on path
+ elif not tmp_dir.endswith("/"):
+ tmp_dir += "/"
+
+ if include_class_id:
+ # Append the classname to the prefix from the class name
+ # so any problematic temp files can be associated with
+ # the class that created them. This should be especially
+ # useful for testing, but is turned off by default to
+ # avoid the string-parsing overhead.
+ class_id = str(self.__class__())
+ prefix = ''.join([prefix,
+ class_id[class_id.rindex('.') + 1:
+ class_id.index(' ')]])
+
+ try:
+ mkdir(tmp_dir)
+ except OSError:
+ # Directory already exists
+ pass
+ # note: it is OK to join FilePath objects with +
+ return result_constructor(tmp_dir) + result_constructor(prefix) + \
+ result_constructor(''.join([choice(_all_chars)
+ for i in range(self.TmpNameLen)])) +\
+ result_constructor(suffix)
+
+
+ at implements_iterator
+class ParameterIterBase(object):
+
+ """Base class for parameter iteration objects
+
+ This class provides base functionality for parameter iteration objects.
+ A parameter iteration object acts like a generator and returns
+ parameter dicts of varying values. The specific keys and ranges of values
+ can be specified. Subclasses of this object implement the way in which
+ the parameter values are chosen."""
+
+ def __init__(self, Application, Parameters, AlwaysOn=None):
+ """Initialize the ParameterIterBase
+
+ Application : A CommandLineApplication subclass
+ Parameters : A dict keyed by the application paramter, value by
+ the range of parameters to enumerate over. For
+ FlagParameters, unless specified in AlwaysOn, the value
+ will cycle between True/False (on/off). For
+ MixedParameters, include [None] specifically to utilize
+ flag functionality.
+ AlwaysOn : List of parameters that will always be on
+
+ Parameters is checked against the applications known parameters, but
+ only performed superficially: only keys are validated. AlwaysOn
+ values must have entries within Parameters.
+
+ NOTE: If the parameter is not specified in AlwaysOn, a False value
+ is appended so that the parameter can be turned off. Multiple False
+ states for a parameter will result if False is specified without
+ adding the parameter to AlwaysOn. If a parameter has a default value,
+ then that parameter is implicitly always on.
+ """
+ self.AppParams = Application._parameters
+
+ # Validate Parameters
+ param_set = set(Parameters.keys())
+ app_param_set = set(self.AppParams.keys())
+ if not param_set.issubset(app_param_set):
+ not_present = str(param_set.difference(app_param_set))
+ raise ValueError(
+ "Parameter(s) %s not present in app" %
+ not_present)
+
+ # Validate AlwaysOn
+ alwayson_set = set(AlwaysOn)
+ if not alwayson_set.issubset(param_set):
+ not_present = str(alwayson_set.difference(param_set))
+ raise ValueError("AlwaysOn value(s) %s not in Parameters" %
+ not_present)
+
+ # Make sure all values are lists
+ for k, v in Parameters.items():
+ if not isinstance(v, list):
+ Parameters[k] = [v]
+ _my_params = Parameters
+
+ # Append "off states" to relevant parameters
+ for k in param_set.difference(alwayson_set):
+ _my_params[k].append(False)
+
+ # Create seperate key/value lists preserving index relation
+ self._keys, self._values = zip(*sorted(_my_params.items()))
+
+ # Construct generator
+ self._generator = self._init_generator()
+
+ def _init_generator(self):
+ """Must be implemented in the subclass"""
+ pass
+
+ def _make_app_params(self, values):
+ """Returns app's param dict with values set as described by values
+ """
+ # A deep copy is necessary. Otherwise the dict values refer to
+ # the same object.
+ app_params = deepcopy(self.AppParams)
+ for key, value in zip(self._keys, values):
+ if value is False:
+ app_params[key].off()
+ elif value is True:
+ app_params[key].on()
+ else:
+ app_params[key].on(value)
+ return app_params
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return next(self._generator)
+
+ def reset(self):
+ self._generator = self._init_generator()
+
+
+class ParameterCombinations(ParameterIterBase):
+
+ """Iterates over all combinations of parameters lexiographically"""
+
+ def _init_generator(self):
+ """Iterates over all possible combinations of parameters
+
+ This method iterates over the cartesian product of parameter values
+ """
+ for vals in product(*self._values):
+ yield self._make_app_params(vals)
+
+
+def cmdline_generator(param_iter, PathToBin=None, PathToCmd=None,
+ PathsToInputs=None, PathToOutput=None,
+ PathToStderr='/dev/null', PathToStdout='/dev/null',
+ UniqueOutputs=False, InputParam=None,
+ OutputParam=None):
+ """Generates command lines that can be used in a cluster environment
+
+ param_iter : ParameterIterBase subclass instance
+ PathToBin : Absolute location primary command (i.e. Python)
+ PathToCmd : Absolute location of the command
+ PathsToInputs : Absolute location(s) of input file(s)
+ PathToOutput : Absolute location of output file
+ PathToStderr : Path to stderr
+ PathToStdout : Path to stdout
+ UniqueOutputs : Generate unique tags for output files
+ InputParam : Application input parameter (if not specified, assumes
+ stdin is to be used)
+ OutputParam : Application output parameter (if not specified, assumes
+ stdout is to be used)
+ """
+ # Make sure we have input(s) and output
+ if not PathsToInputs:
+ raise ValueError("No input file(s) specified.")
+ if not PathToOutput:
+ raise ValueError("No output file specified.")
+
+ if not isinstance(PathsToInputs, list):
+ PathsToInputs = [PathsToInputs]
+
+ # PathToBin and PathToCmd can be blank
+ if PathToBin is None:
+ PathToBin = ''
+ if PathToCmd is None:
+ PathToCmd = ''
+
+ # stdout_ and stderr_ do not have to be redirected
+ if PathToStdout is None:
+ stdout_ = ''
+ else:
+ stdout_ = '> "%s"' % PathToStdout
+ if PathToStderr is None:
+ stderr_ = ''
+ else:
+ stderr_ = '2> "%s"' % PathToStderr
+
+ # Output can be redirected to stdout or specified output argument
+ if OutputParam is None:
+ output = '> "%s"' % PathToOutput
+ stdout_ = ''
+ else:
+ output_param = param_iter.AppParams[OutputParam]
+ output_param.on('"%s"' % PathToOutput)
+ output = str(output_param)
+ output_param.off()
+
+ output_count = 0
+ base_command = ' '.join([PathToBin, PathToCmd])
+ for params in param_iter:
+ # Support for multiple input files
+ for inputfile in PathsToInputs:
+ cmdline = [base_command]
+ cmdline.extend(sorted(filter(None, map(str, params.values()))))
+
+ # Input can come from stdin or specified input argument
+ if InputParam is None:
+ input = '< "%s"' % inputfile
+ else:
+ input_param = params[InputParam]
+ input_param.on('"%s"' % inputfile)
+ input = str(input_param)
+ input_param.off()
+
+ cmdline.append(input)
+
+ if UniqueOutputs:
+ cmdline.append(''.join([output, str(output_count)]))
+ output_count += 1
+ else:
+ cmdline.append(output)
+
+ cmdline.append(stdout_)
+ cmdline.append(stderr_)
+
+ yield ' '.join(cmdline)
+
+
+def get_tmp_filename(tmp_dir=gettempdir(), prefix="tmp", suffix=".txt",
+ result_constructor=FilePath):
+ """ Generate a temporary filename and return as a FilePath object
+
+ tmp_dir: the directory to house the tmp_filename
+ prefix: string to append to beginning of filename
+ Note: It is very useful to have prefix be descriptive of the
+ process which is creating the temporary file. For example, if
+ your temp file will be used to build a temporary blast database,
+ you might pass prefix=TempBlastDB
+ suffix: the suffix to be appended to the temp filename
+ result_constructor: the constructor used to build the result filename
+ (default: cogent.app.parameters.FilePath). Note that joining
+ FilePath objects with one another or with strings, you must use
+ the + operator. If this causes trouble, you can pass str as the
+ the result_constructor.
+ """
+ # check not none
+ if not tmp_dir:
+ tmp_dir = ""
+ # if not current directory, append "/" if not already on path
+ elif not tmp_dir.endswith("/"):
+ tmp_dir += "/"
+
+ chars = "abcdefghigklmnopqrstuvwxyz"
+ picks = chars + chars.upper() + "0123456790"
+ return result_constructor(tmp_dir) + result_constructor(prefix) +\
+ result_constructor("%s%s" %
+ (''.join([choice(picks) for i in range(20)]),
+ suffix))
+
+
+def guess_input_handler(seqs, add_seq_names=False):
+ """Returns the name of the input handler for seqs."""
+ if isinstance(seqs, str):
+ if '\n' in seqs: # can't be a filename...
+ return '_input_as_multiline_string'
+ else: # assume it was a filename
+ return '_input_as_string'
+
+ if isinstance(seqs, list) and len(seqs) and isinstance(seqs[0], tuple):
+ return '_input_as_seq_id_seq_pairs'
+
+ if add_seq_names:
+ return '_input_as_seqs'
+
+ return '_input_as_lines'
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 5f426c8..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,18 +0,0 @@
-python-burrito (0.9.1-1) unstable; urgency=medium
-
- * Initial upload to Debian (Closes: #793968)
-
- -- Andreas Tille <tille at debian.org> Wed, 29 Jul 2015 15:40:04 +0200
-
-python-burrito (0.9.0-0biolinux3) trusty; urgency=medium
-
- * Add missing build deps on dh_python, setuptools, nose
-
- -- Tim Booth <tbooth at ceh.ac.uk> Mon, 02 Mar 2015 19:08:33 +0000
-
-python-burrito (0.9.0-0biolinux1) trusty; urgency=medium
-
- * Initial release for QIIME 1.9
- * Build for Python2 only just now.
-
- -- Tim Booth <tbooth at ceh.ac.uk> Fri, 20 Feb 2015 18:10:56 +0000
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 7401337..0000000
--- a/debian/control
+++ /dev/null
@@ -1,54 +0,0 @@
-Source: python-burrito
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Tim Booth <tbooth at ceh.ac.uk>,
- Andreas Tille <tille at debian.org>
-Section: python
-Priority: optional
-Build-Depends: debhelper (>= 9),
- python-all (>= 2.7),
- dh-python,
- python-future,
- python-setuptools,
- python-nose,
- python3-all,
- python3-future,
- python3-setuptools,
- python3-nose
-Standards-Version: 3.9.6
-Vcs-Browser: http://anonscm.debian.org/viewvc/debian-med/trunk/packages/python-burrito/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/python-burrito/trunk/
-Homepage: https://github.com/biocore/burrito
-
-Package: python-burrito
-Architecture: all
-Depends: ${shlibs:Depends},
- ${misc:Depends},
- ${python:Depends}
-Description: Python 2 framework for wrapping and controlling command-line applications
- burrito, canonically pronounced boar-eee-toe, is a Python framework for
- wrapping and controlling command-line applications.
- .
- This tool allows developers to wrap command-line applications, just as burritos
- wrap delicious foods. Both hide the potentially unsightly details.
- .
- burrito is derived from the application controller framework code, which was
- originally added to PyCogent and later moved to scikit-bio.
- .
- This package provides the Python 2 modules.
-
-Package: python3-burrito
-Architecture: all
-Depends: ${shlibs:Depends},
- ${misc:Depends},
- ${python3:Depends}
-Description: Python 3 framework for wrapping and controlling command-line applications
- burrito, canonically pronounced boar-eee-toe, is a Python framework for
- wrapping and controlling command-line applications.
- .
- This tool allows developers to wrap command-line applications, just as burritos
- wrap delicious foods. Both hide the potentially unsightly details.
- .
- burrito is derived from the application controller framework code, which was
- originally added to PyCogent and later moved to scikit-bio.
- .
- This package provides the Python 3 modules.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 2114ebe..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,40 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: burrito
-Upstream-Contact: gregcaporaso at gmail.com
-Source: https://github.com/biocore/burrito/
-
-Files: *
-Copyright: 2014-2015 burrito development team <gregcaporaso at gmail.com>
-License: BSD-3-clause
-
-Files: debian/*
-Copyright: 2015 Tim Booth <tbooth at ceh.ac.uk>
- Andreas Tille <tille at debian.org>
-License: BSD-3-clause
-
-License: BSD-3-clause
- 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 names burrito or biocore 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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/patches/better_error_reporting b/debian/patches/better_error_reporting
deleted file mode 100644
index 00e5872..0000000
--- a/debian/patches/better_error_reporting
+++ /dev/null
@@ -1,24 +0,0 @@
-Author: Tim Booth <tbooth at ceh.ac.uk>
-Last-Update: Fri, 20 Feb 2015 18:10:56 +0000
-Description: More verbose error reporting
-
---- a/burrito/util.py
-+++ b/burrito/util.py
-@@ -297,10 +297,15 @@ class CommandLineApplication(Application
- result = \
- CommandLineAppResult(out, err, exit_status,
- result_paths=result_paths)
-- except ApplicationError:
-- result = \
-+ except ApplicationError as e1:
-+ try:
-+ result = \
- self._handle_app_result_build_failure(out, err, exit_status,
- result_paths)
-+ except ApplicationError:
-+ #If the handler just throws an error then report the first error,
-+ #not the new error.
-+ raise e1
-
- # Clean up the input file if one was created
- if remove_tmp:
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index 886711b..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1 +0,0 @@
-better_error_reporting
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index ca37898..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/make -f
-# -*- makefile -*-
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-export PYBUILD_NAME=burrito
-
-%:
- dh $@ --with python2,python3 --buildsystem=pybuild
-
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 ac55a54..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,2 +0,0 @@
-version=3
-https://pypi.python.org/pypi/burrito .*/burrito-([0-9.]+).tar.[xgb]z2?
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..8940668
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+# ----------------------------------------------------------------------------
+# Copyright (c) 2014--, burrito development team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# ----------------------------------------------------------------------------
+
+from setuptools import find_packages, setup
+
+__version__ = "0.9.1"
+
+classes = """
+ Development Status :: 5 - Production/Stable
+ Intended Audience :: Developers
+ License :: OSI Approved :: BSD License
+ Topic :: Software Development :: Libraries
+ Topic :: Software Development :: Libraries :: Python Modules
+ Topic :: Utilities
+ Programming Language :: Python
+ Programming Language :: Python :: 2
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.3
+ Programming Language :: Python :: 3.4
+ Operating System :: Unix
+ Operating System :: POSIX
+ Operating System :: MacOS :: MacOS X
+"""
+classifiers = [s.strip() for s in classes.split('\n') if s]
+
+description = ('Framework for wrapping and controlling command-line '
+ 'applications.')
+
+with open('README.rst') as f:
+ long_description = f.read()
+
+setup(name='burrito',
+ version=__version__,
+ license='BSD',
+ description=description,
+ long_description=long_description,
+ author="burrito development team",
+ author_email="gregcaporaso at gmail.com",
+ maintainer="burrito development team",
+ maintainer_email="gregcaporaso at gmail.com",
+ url='https://github.com/biocore/burrito',
+ test_suite='nose.collector',
+ packages=find_packages(),
+ install_requires=['future'],
+ extras_require={'test': ["nose >= 0.10.1", "flake8",
+ "coveralls"]},
+ classifiers=classifiers)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-burrito.git
More information about the debian-med-commit
mailing list