This is an automated email from the git hooks/post-receive script. tille pushed a commit to branch master in repository python-galaxyxml.
commit 9bbb0a5ea094eed53a61c1c8c07bdfaf4b682614 Author: Anton Khodak <[email protected]> Date: Sat Jan 14 15:43:42 2017 +0200 Imported Upstream version 0.1 --- .gitignore | 67 ++++++ README.md | 20 ++ examples/example.py | 51 +++++ examples/tool.xml | 20 ++ galaxyxml/__init__.py | 47 ++++ galaxyxml/tool/__init__.py | 103 +++++++++ galaxyxml/tool/parameters/__init__.py | 400 ++++++++++++++++++++++++++++++++++ parser.py | 119 ++++++++++ requirements.txt | 1 + setup.py | 19 ++ 10 files changed, 847 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47af8f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +# Created by https://www.gitignore.io + +### vim ### +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..32dcbe7 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Galaxy XML Generation Libraries + +These libraries will support building of Tool XML and Tool Dependencies XML. +We'd be happy to support any other XML that Galaxy supports, just make an issue +or PR if you're feeling motivated. + +## Status + +- ToolXML is mostly supported, there + +## Known Bugs + +- no validation of unique names +- repeats aren't named properly +- conditional/whens aren't named properly +- conditionals not handled in CLI + +## License + +- Apache License, v2 diff --git a/examples/example.py b/examples/example.py new file mode 100644 index 0000000..c97d48a --- /dev/null +++ b/examples/example.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +import galaxyxml.tool as gxt +import galaxyxml.tool.parameters as gxtp + +tool = gxt.Tool("aragorn", 'se.lu.mbioekol.mbio-serv2.aragorn', + "1.2.36", "Aragorn is a tRNA finder", "aragorn.exe") + +inputs = gxtp.Inputs() +outputs = gxtp.Outputs() + +# A parameter +param = gxtp.BooleanParam('flag', label='Flag label', help='Flag help', num_dashes=1) +# Yes I know this is rubbish. Please make a PR!! +param.space_between_arg = ' ' +inputs.append(param) + + +# A float +param = gxtp.FloatParam('float', label='Float label', + help='Float help', value=0, num_dashes=1) +param.space_between_arg = ' ' +inputs.append(param) + + +param_min = gxtp.IntegerParam('int_min', + label='int_min label', + help='int_min help', value=0, num_dashes=1) +param_max = gxtp.IntegerParam('int_max', + label='int_max label', + help='int_max help', value=0, num_dashes=1) +param_min.command_line_override = '-i$int_min,$int_max' +param_max.command_line_override = '' +param_min.space_between_arg = ' ' +param_max.space_between_arg = ' ' +inputs.append(param_min) +inputs.append(param_max) + + +# Outputs +param = gxtp.OutputParameter('output', format="tabular", num_dashes=1) +param.space_between_arg = ' ' +outputs.append(param) + +tool.inputs = inputs +tool.outputs = outputs +tool.help = 'HI' + +data = tool.export() + +with open('tool.xml', 'w') as handle: + handle.write(data) diff --git a/examples/tool.xml b/examples/tool.xml new file mode 100644 index 0000000..a7c64a8 --- /dev/null +++ b/examples/tool.xml @@ -0,0 +1,20 @@ +<tool force_history_refresh="false" hidden="false" id="se.lu.mbioekol.mbio-serv2.aragorn" name="aragorn" version="1.2.36" workflow_compatible="true"> + <description>Aragorn is a tRNA finder</description> + <stdio> + <exit_code level="fatal" range="1:"/> + </stdio> + <command><![CDATA[aragorn.exe $flag +-float $float +-i$int_min,$int_max +-output $output]]></command> + <inputs> + <param checked="false" help="(-flag) Flag help" label="Flag label" name="flag" type="boolean" truevalue="-flag"/> + <param help="(-float) Float help" label="Float label" name="float" type="float" value="0"/> + <param help="(-int_min) int_min help" label="int_min label" name="int_min" type="integer" value="0"/> + <param help="(-int_max) int_max help" label="int_max label" name="int_max" type="integer" value="0"/> + </inputs> + <outputs> + <data format="tabular" hidden="false" name="output"/> + </outputs> + <help><![CDATA[HI]]></help> +</tool> diff --git a/galaxyxml/__init__.py b/galaxyxml/__init__.py new file mode 100644 index 0000000..0d2e959 --- /dev/null +++ b/galaxyxml/__init__.py @@ -0,0 +1,47 @@ +from lxml import etree + +class GalaxyXML(object): + + def __init__(self): + self.root = etree.Element('root') + + def export(self): + return etree.tostring(self.root, pretty_print=True) + + +class Util(object): + + @classmethod + def coerce(cls, data): + """Recursive data sanitisation + """ + if isinstance(data, dict): + return {k: cls.coerce(v) for k, v in data.items() if v is not None} + elif isinstance(data, list): + return [cls.coerce(v) for v in data] + else: + return cls.coerce_value(data) + + @classmethod + def coerce_value(cls, obj): + """Make everything a string! + """ + if isinstance(obj, bool): + if obj: + return "true" + else: + return "false" + elif isinstance(obj, str): + return obj + else: + return str(obj) + + @classmethod + def clean_kwargs(cls, params): + if 'kwargs' in params: + kwargs = params['kwargs'] + for k in kwargs: + params[k] = kwargs[k] + del params['kwargs'] + del params['self'] + return params diff --git a/galaxyxml/tool/__init__.py b/galaxyxml/tool/__init__.py new file mode 100644 index 0000000..6e077df --- /dev/null +++ b/galaxyxml/tool/__init__.py @@ -0,0 +1,103 @@ +from lxml import etree +from galaxyxml import Util, GalaxyXML +from galaxyxml.tool.parameters import XMLParam + +VALID_TOOL_TYPES = ('data_source', 'data_source_async') +VALID_URL_METHODS = ('get', 'post') + + +class Tool(GalaxyXML): + + def __init__(self, name, id, version, description, executable, hidden=False, + tool_type=None, URL_method=None, workflow_compatible=True, + force_history_refresh=False, interpreter=None): + + self.executable = executable + self.interpreter = interpreter + kwargs = { + 'name': name, + 'id': id, + 'version': version, + 'hidden': hidden, + 'workflow_compatible': workflow_compatible, + 'force_history_refresh': force_history_refresh, + } + kwargs = Util.coerce(kwargs) + self.root = etree.Element('tool', **kwargs) + + if tool_type is not None: + if tool_type not in VALID_TOOL_TYPES: + raise Exception("Tool type must be one of %s" % ','.join(VALID_TOOL_TYPES)) + else: + kwargs['tool_type'] = tool_type + + if URL_method is not None: + if URL_method in VALID_URL_METHODS: + kwargs['URL_method'] = URL_method + else: + raise Exception("URL_method must be one of %s" % + ','.join(VALID_URL_METHODS)) + + description_node = etree.SubElement(self.root, 'description') + description_node.text = description + + def version_command(self, command_string): + version_command = etree.SubElement(self.root, 'version_command') + version_command.text = version_command + + def append(self, sub_node): + if issubclass(type(sub_node), XMLParam): + self.root.append(sub_node.node) + else: + self.root.append(sub_node) + + def clean_command_string(self, command_line): + clean = [] + for x in command_line: + if x is not [] and x is not ['']: + clean.append(x) + + return '\n'.join(clean) + + def export(self): + command_line = [] + try: + command_line.append(self.inputs.cli()) + except Exception, e: + print e + + try: + command_line.append(self.outputs.cli()) + except: + pass + + # Add stdio section + stdio = etree.SubElement(self.root, 'stdio') + etree.SubElement(stdio, 'exit_code', range='1:', level='fatal') + + # Steal interpreter from kwargs + command_kwargs = {} + if self.interpreter is not None: + command_kwargs['interpreter'] = self.interpreter + + # Add command section + command_node = etree.SubElement(self.root, 'command', **command_kwargs) + + actual_cli = "%s %s" % (self.executable, self.clean_command_string(command_line)) + command_node.text = etree.CDATA(actual_cli.strip()) + + + try: + self.append(self.inputs) + except: + pass + + try: + self.append(self.outputs) + except: + pass + + help_element = etree.SubElement(self.root, 'help') + help_element.text = etree.CDATA(self.help) + + return super(Tool, self).export() diff --git a/galaxyxml/tool/parameters/__init__.py b/galaxyxml/tool/parameters/__init__.py new file mode 100644 index 0000000..1703adb --- /dev/null +++ b/galaxyxml/tool/parameters/__init__.py @@ -0,0 +1,400 @@ +from lxml import etree +from galaxyxml import Util + +class XMLParam(object): + name = 'node' + + def __init__(self, *args, **kwargs): + # http://stackoverflow.com/a/12118700 + self.children = [] + kwargs = {k: v for k, v in kwargs.items() if v is not None} + kwargs = Util.coerce(kwargs) + self.node = etree.Element(self.name, **kwargs) + + def append(self, sub_node): + if self.acceptable_child(sub_node): + # If one of ours, they aren't etree nodes, they're custom objects + if issubclass(type(sub_node), XMLParam): + self.node.append(sub_node.node) + self.children.append(sub_node) + else: + raise Exception("Child was unacceptable to parent (%s is not appropriate for %s)" % (type(self), type(sub_node))) + else: + raise Exception("Child was unacceptable to parent (%s is not appropriate for %s)" % (type(self), type(sub_node))) + + def validate(self): + # Very few need validation, but some nodes we may want to have + # validation routines on. Should only be called when DONE. + for child in self.children: + # If any child fails to validate return false. + if not child.validate(): + return False + return True + + def cli(self): + lines = [] + for child in self.children: + lines.append(child.command_line()) + #lines += child.command_line() + return '\n'.join(lines) + + def command_line(self): + return None + + +class RequestParamTranslation(XMLParam): + name = 'request_param_translation' + + def __init__(self, **kwargs): + self.node = etree.Element(self.name) + + def acceptable_child(self, child): + return isinstance(child, RequestParamTranslation) + + +class RequestParam(XMLParam): + name = 'request_param' + + def __init__(self, galaxy_name, remote_name, missing, **kwargs): + #TODO: bulk copy locals into self.attr? + self.galaxy_name = galaxy_name + # http://stackoverflow.com/a/1408860 + params = Util.clean_kwargs(locals().copy()) + super(RequestParam, self).__init__(**params) + + def acceptable_child(self, child): + return isinstance(child, AppendParam) and self.galaxy_name == "URL" + + +class AppendParam(XMLParam): + name = 'append_param' + + def __init__(self, separator="&", first_separator="?", join="=", **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(AppendParam, self).__init__(**params) + + def acceptable_child(self, child): + return isinstance(child, AppendParamValue) + + +class AppendParamValue(XMLParam): + name = 'value' + + def __init__(self, name="_export", missing="1", **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(AppendParamValue, self).__init__(**params) + + def acceptable_child(self, child): + return False + + +class Inputs(XMLParam): + name = 'inputs' + # This bodes to be an issue -__- + + def acceptable_child(self, child): + return issubclass(type(child), InputParameter) + + +class InputParameter(XMLParam): + + def __init__(self, name, **kwargs): + # TODO: look at + self.mako_identifier = name + # We use kwargs instead of the usual locals(), so manually copy the + # name to kwargs + kwargs['name'] = name + + # Handle positional parameters + if 'positional' in kwargs and kwargs['positional']: + self.positional = True + else: + self.positional = False + + if 'num_dashes' in kwargs: + self.num_dashes = kwargs['num_dashes'] + del kwargs['num_dashes'] + else: + self.num_dashes = 0 + + self.space_between_arg = " " + + # Not sure about this :( + # https://wiki.galaxyproject.org/Tools/BestPractices#Parameter_help + if 'label' in kwargs: + # TODO: replace with positional attribute + if len(self.flag()) > 0: + if kwargs['label'] is None: + kwargs['label'] = 'Author did not provide help for this parameter... ' + if not self.positional: + kwargs['label'] += ' (%s)' % self.flag() + + super(InputParameter, self).__init__(**kwargs) + + def command_line(self): + before = self.command_line_before() + cli = self.command_line_actual() + after = self.command_line_after() + + complete = [x for x in (before, cli, after) if x is not None] + return '\n'.join(complete) + + def command_line_before(self): + return None + + def command_line_after(self): + return None + + def command_line_actual(self): + if hasattr(self, 'command_line_override'): + return self.command_line_override + else: + if self.positional: + return self.mako_name() + else: + return "%s%s%s" % (self.flag(), self.space_between_arg, self.mako_name()) + + def mako_name(self): + # TODO: enhance logic to check up parents for things like repeat>condotion>param + return '$' + self.mako_identifier + + def flag(self): + flag = '-' * self.num_dashes + return flag + self.mako_identifier + + +class Repeat(InputParameter): + name = 'repeat' + + def __init__(self, name, title, min=None, max=None, default=None, + **kwargs): + params = Util.clean_kwargs(locals().copy()) + # Allow overriding + self.cli_before = '#for $i in $%s' % name + self.cli_after = '#end for' + super(Repeat, self).__init__(**params) + + def acceptable_child(self, child): + return issubclass(type(child), InputParameter) + + def command_line_before(self): + return self.cli_before + + def command_line_after(self): + return self.cli_after + + def command_line_actual(self): + if hasattr(self, 'command_line_override'): + return self.command_line_override + else: + return "%s" % self.mako_name() + +class Conditional(InputParameter): + name = 'conditional' + + def __init__(self, name, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(Conditional, self).__init__(**params) + + def acceptable_child(self, child): + return issubclass(type(child), InputParameter) \ + and not isinstance(child, Conditional) + + def validate(self): + # Find a way to check if one of the kids is a WHEN + pass + + +class Param(InputParameter): + name = 'param' + + # This...isn't really valid as-is, and shouldn't be used. + def __init__(self, name, optional=None, label=None, help=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + params['type'] = self.type + super(Param, self).__init__(**params) + + if type(self) == Param: + raise Exception("Param class is not an actual parameter type, use a subclass of Param") + + def acceptable_child(self, child): + return issubclass(type(child, InputParameter) or isinstance(child), ValidatorParam) + + +class TextParam(Param): + type = 'text' + + def __init__(self, name, optional=None, label=None, help=None, size=None, + area=False, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(TextParam, self).__init__(**params) + + +class _NumericParam(Param): + + def __init__(self, name, value, optional=None, label=None, help=None, + min=None, max=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(_NumericParam, self).__init__(**params) + + +class IntegerParam(_NumericParam): + type = 'integer' + + +class FloatParam(_NumericParam): + type = 'float' + + +class BooleanParam(Param): + type = 'boolean' + + def __init__(self, name, optional=None, label=None, help=None, + checked=False, truevalue=None, falsevalue=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + + super(BooleanParam, self).__init__(**params) + if truevalue is None and falsevalue is None: + # If truevalue and falsevalue are None, then we use "auto", the IUC + # recommended default. + # + # truevalue is set to the parameter's value, and falsevalue is not. + # + # Unfortunately, mako_identifier is set as a result of the super + # call, which we shouldn't call TWICE, so we'll just hack around this :( + #params['truevalue'] = '%s%s' % (self.) + self.node.attrib['truevalue'] = self.flag() + + + def command_line_actual(self): + if hasattr(self, 'command_line_override'): + return self.command_line_override + else: + return "%s" % self.mako_name() + + +class DataParam(Param): + type = 'data' + + def __init__(self, name, optional=None, label=None, help=None, format=None, + multiple=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(DataParam, self).__init__(**params) + + +class SelectParam(Param): + type = 'data' + + def __init__(self, name, optional=None, label=None, help=None, + data_ref=None, display=None, multiple=None, options=None, + default=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + del params['options'] + del params['default'] + + super(DataParam, self).__init__(**params) + + if options is not None and default is not None: + if default not in options: + raise Exception("Specified a default that isn't in options") + + for k,v in options: + selected = (k == default) + self.append(SelectOption(k, v, selected=selected)) + + +class SelectOption(InputParameter): + name = 'option' + + def __init__(self, value, text, selected=False, **kwargs): + params = Util.clean_kwargs(locals().copy()) + del params['text'] + super(SelectOption, self).__init__(**params) + self.node.text = text + + +class ValidatorParam(InputParameter): + name = 'validator' + + def __init__(self, type, message=None, filename=None, metadata_name=None, + metadata_column=None, line_startswith=None, min=None, + max=None, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(ValidatorParam, self).__init__(**params) + + +class Outputs(XMLParam): + name = 'outputs' + + def acceptable_child(self, child): + return issubclass(type(child), OutputParameter) + + +class OutputParameter(XMLParam): + """Copypasta of InputParameter, needs work + """ + name = 'data' + + def __init__(self, name, format, format_source=None, metadata_source=None, + label=None, from_work_dir=None, hidden=False, **kwargs): + # TODO: validate format_source&metadata_source against something in the + # XMLParam children tree. + self.mako_identifier = name + if 'num_dashes' in kwargs: + self.num_dashes = kwargs['num_dashes'] + del kwargs['num_dashes'] + else: + self.num_dashes = 0 + self.space_between_arg = " " + params = Util.clean_kwargs(locals().copy()) + + super(OutputParameter, self).__init__(**params) + + def command_line(self): + if hasattr(self, 'command_line_override'): + return self.command_line_override + else: + return "%s%s%s" % (self.flag(), self.space_between_arg, self.mako_name()) + + def mako_name(self): + return '$' + self.mako_identifier + + def flag(self): + flag = '-' * self.num_dashes + return flag + self.mako_identifier + + def acceptable_child(self, child): + return isinstance(child, OutputFilter) or isinstance(child, ChangeFormat) + +class OutputFilter(XMLParam): + name = 'filter' + + def __init__(self, text, **kwargs): + params = Util.clean_kwargs(locals().copy()) + del params['text'] + super(OutputFilter, self).__init__(**params) + self.node.text = text + + def acceptable_child(self, child): + return False + +class ChangeFormat(XMLParam): + name = 'change_format' + + def __init__(self, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(ChangeFormat, self).__init__(**params) + + def acceptable_child(self, child): + return isinstance(child, ChangeFormatWhen) + + +class ChangeFormatWhen(XMLParam): + name = 'when' + + def __init__(self, input, format, value, **kwargs): + params = Util.clean_kwargs(locals().copy()) + super(ChangeFormatWhen, self).__init__(**params) + + def acceptable_child(self, child): + return False diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..e822ef6 --- /dev/null +++ b/parser.py @@ -0,0 +1,119 @@ +import re + +def start_pattern(string): + return string.startswith(' -') + + +CLI = { + 'OUTPUT': (re.compile(r'^\s*(?P<dash>--?)(?P<name>[A-Za-z0-9]+)(?P<space_between_arg>[ ]*)<(?P<args>out[^>]*)>\s+(?P<content>.*)'), {'nargs': 1}), + 'FLAG': (re.compile(r'^\s*(?P<dash>--?)(?P<name>[A-Za-z0-9]+)[ ]{2,}(?P<content>.*)'), {'nargs': 0}), + 'PARAM': (re.compile(r'^\s*(?P<dash>--?)(?P<name>[A-Za-z0-9]+)(?P<space_between_arg>[ ]*)<(?P<args>[^>]*)>\s+(?P<content>.*)'), {'nargs': 1}), + 'PARAM_RANGE': (re.compile(r'^\s*(?P<dash>--?)(?P<name>[A-Za-z0-9]+)(?P<space_between_arg>[ ]*)<(?P<args>[^>]*)>,<(?P<args2>[^>]*)>\s+(?P<content>.*)'), {'nargs': 2}), +} + +CLI_GROUPS = ('name', 'args', 'content', 'args2', 'dash', 'space_between_arg') +CLI_DEFAULTS = { + 'space_between_arg': ' ' +} + +def detect_args(cli): + for matcher in CLI: + matches = CLI[matcher][0].search(cli) + if matches: + hit = CLI[matcher][1] + hit['type'] = matcher + + # Apply defaults. Poorly organised. + for default in CLI_DEFAULTS: + hit[default] = CLI_DEFAULTS[default] + + # Actual groups in regex + for group in CLI_GROUPS: + try: + hit[group] = matches.group(group) + except IndexError: + # Ignore missing groups + pass + return hit + return None + +def reflow_block(block): + single = ' '.join([x.strip() for x in block]) + return detect_args(single) + +def blocks(iterable): + accumulator = [] + for line in iterable: + if start_pattern(line): + if accumulator: + yield reflow_block(accumulator) + accumulator = [line] + else: + accumulator.append(line) + if accumulator: + yield reflow_block(accumulator) + +import galaxyxml.tool as gxt +import galaxyxml.tool.parameters as gxtp + +tool = gxt.Tool(name="aragorn", version="1.2.36", description="tRNA finder", + executable="aragorn") +inputs = gxtp.Inputs() +outputs = gxtp.Outputs() +for line in blocks(open('tmp','r').readlines()): + if line and line['type'] == 'FLAG': + param = gxtp.BooleanParam(line['name'], label=line['name'], help=line['content'], + #truevalue=cli_param, falsevalue="") + ) + param.num_dashes = len(line['dash']) + param.space_between_arg = line['space_between_arg'] + inputs.append(param) + elif line and line['type'] == 'PARAM': + print "%(dash)s%(name)s <%(args)s> %(content)s" % line + answer = raw_input("Is this a FLOAT, INT, or TEXT input? ") + if 'FLOAT' in answer: + param = gxtp.FloatParam(line['name'], label=line['name'], + help=line['content'], value=0) + elif 'INT' in answer: + param = gxtp.IntegerParam(line['name'], label=line['name'], + help=line['content'], value=0) + elif 'TEXT' in answer: + param = gxtp.TextParam(line['name'], label=line['name'], + help=line['content']) + try: + param.num_dashes = len(line['dash']) + param.space_between_arg = line['space_between_arg'] + inputs.append(param) + except: + print "Uhoh, bad answer" + print + elif line and line['type'] == 'PARAM_RANGE': + param_min = gxtp.IntegerParam(line['name']+'_min', + label=line['name']+'_min', + help=line['content'], value=0) + param_max = gxtp.IntegerParam(line['name']+'_max', + label=line['name']+'_max', + help=line['content'], value=0) + param_min.command_line_override = '-i$i_min,$i_max' + param_max.command_line_override = '' + param_min.num_dashes = len(line['dash']) + param_max.num_dashes = len(line['dash']) + param_min.space_between_arg = line['space_between_arg'] + param_max.space_between_arg = line['space_between_arg'] + inputs.append(param_min) + inputs.append(param_max) + elif line and line['type'] == 'OUTPUT': + param = gxtp.OutputParameter(line['name'], format="tabular") + param.num_dashes = len(line['dash']) + param.space_between_arg = line['space_between_arg'] + outputs.append(param) + +tool.inputs = inputs +tool.outputs = outputs +tool.help = 'HI' + + +data = tool.export() + +with open('tool.xml', 'w') as handle: + handle.write(data) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ab90481 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +lxml diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..90a9146 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup + +requirements = [x.strip() for x in open('requirements.txt', 'r').readlines()] + +setup(name="galaxyxml", + version='0.1', + description='Galaxy XML generation library', + author='Eric Rasche', + author_email='[email protected]', + license='GPL3', + install_requires=requirements, + packages=["galaxyxml", "galaxyxml.tool", "galaxyxml.tool.parameters"], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Operating System :: OS Independent', + 'Intended Audience :: Developers', + 'Environment :: Console', + ], + ) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-galaxyxml.git _______________________________________________ debian-med-commit mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit
