changeset 0:bc00f5c4c59e draft

planemo upload for repository https://github.com/Yating-L/jbrowse-archive-creator.git
author yating-l
date Tue, 17 Oct 2017 17:28:05 -0400
parents
children 2ae1e96a8380
files apollo/ApolloInstance.py apollo/ApolloInstance.pyc apollo/ApolloUser.py apollo/ApolloUser.pyc apollo/__init__.py apollo/__init__.pyc jbrowsehubToApollo.py jbrowsehubToApollo.xml templates/__init__.py templates/apollo-arrow.yml templates/custom_track_styles.css tool_dependencies.xml util/Reader.py util/Reader.pyc util/__init__.py util/__init__.pyc util/santitizer.py util/santitizer.pyc util/subtools.py util/subtools.pyc
diffstat 17 files changed, 825 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apollo/ApolloInstance.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+import os
+import json
+import shutil
+import tempfile
+import logging
+from util import subtools
+from mako.lookup import TemplateLookup
+
+
+class ApolloInstance(object):
+    def __init__(self, apollo_host, tool_directory, user_email):
+        self.apollo_host = apollo_host
+        self.tool_directory = tool_directory
+        self.default_user = user_email
+        self.logger = logging.getLogger(__name__)
+        self.apolloTemplate = self._getApolloTemplate()
+        self._arrow_init()
+    
+    #TODO: Encode password
+    def _arrow_init(self):
+        arrow_config = tempfile.NamedTemporaryFile(bufsize=0)
+        with open(arrow_config.name, 'w') as conf:
+            htmlMakoRendered = self.apolloTemplate.render(
+            apollo_host = self.apollo_host,
+            admin_user = self.default_user,
+            admin_pw = '1234'
+        )
+            conf.write(htmlMakoRendered)
+
+        home_dir = os.path.expanduser('~')
+        arrow_config_dir = os.path.join(home_dir, '.apollo-arrow.yml')
+        shutil.copyfile(arrow_config.name, arrow_config_dir)
+        self.logger.debug("Initated arrow: apollo-arrow.yml= %s", arrow_config_dir)
+
+
+    def _getApolloTemplate(self):
+        mylookup = TemplateLookup(directories=[os.path.join(self.tool_directory, 'templates')],
+                                  output_encoding='utf-8', encoding_errors='replace')
+        apolloTemplate = mylookup.get_template("apollo-arrow.yml")
+        return apolloTemplate
+
+    def getHost(self):
+        return self.apollo_host
+
+    def createApolloUser(self, apollo_user, admin=None):
+        p = subtools.arrow_create_user(apollo_user.user_email, apollo_user.firstname, apollo_user.lastname, apollo_user.password, admin) 
+        user_info = json.loads(p)
+        user_id = user_info.get('userId')
+        if not user_id:
+            self.logger.debug("Cannot create new user: %s; The user may already exist", apollo_user.user_email)
+            user_id = subtools.arrow_get_users(apollo_user.user_email)
+        self.logger.debug("Got user_id for new or existing user: user_id = %s", str(user_id))
+        return user_id   
+
+    def grantPermission(self, user_id, organism_id, **user_permissions):
+        subtools.arrow_update_organism_permissions(user_id, organism_id, **user_permissions)
+        self.logger.debug("Grant user %s permissions to organism %s, permissions = %s", str(user_id), str(organism_id), ','.join(user_permissions))
+
+    def addOrganism(self, organism_name, organism_dir):
+        p = subtools.arrow_add_organism(organism_name, organism_dir)
+        organism = json.loads(p)
+        organism_id = organism['id']
+        self.logger.debug("Added new organism to Apollo instance, %s", p)
+        return organism_id
+
+    def loadHubToApollo(self, apollo_user, organism_name, organism_dir, admin_user=False, **user_permissions):
+        user_id = self.createApolloUser(apollo_user, admin_user)
+        organism_id = self.addOrganism(organism_name, organism_dir)
+        self.grantPermission(user_id, organism_id, **user_permissions)
+        self.logger.debug("Successfully load the hub to Apollo")
\ No newline at end of file
Binary file apollo/ApolloInstance.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apollo/ApolloUser.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+import os
+
+class ApolloUser(object):
+    def __init__(self, user_email, firstname, lastname, password):
+        self.user_email = user_email
+        self.firstname = firstname
+        self.lastname = lastname
+        self.password = password
Binary file apollo/ApolloUser.pyc has changed
Binary file apollo/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jbrowsehubToApollo.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+import os
+import sys
+import argparse
+import json
+import logging
+import collections
+from apollo.ApolloInstance import ApolloInstance
+from util.Reader import Reader
+
+
+def main(argv):
+    parser = argparse.ArgumentParser(description='Upload a hub to display on Apollo.')
+    parser.add_argument('-j', '--data_json', help='JSON file containing the metadata of the inputs')
+    parser.add_argument('-o', '--output', help='HTML output')
+    
+    #parser.add_argument('-e', '--extra_file_path', help='Extra file path for generated jbrowse hub')
+    #parser.add_argument('-d', '--jbrowsehub', help='Name of the HTML summarizing the content of the JBrowse Hub Archive')
+
+    # Get the args passed in parameter
+    args = parser.parse_args()
+    json_inputs_data = args.data_json
+    outputFile = args.output
+    #outputFile = args.jbrowsehub
+    
+    
+    ##Parse JSON file with Reader
+    reader = Reader(json_inputs_data)
+
+    # Begin init variables
+    extra_files_path = reader.getExtFilesPath()
+    user_email = reader.getUserEmail() 
+    genome_name = reader.getGenomeName()
+    species_name = reader.getSpeciesName() 
+    apollo_host = reader.getApolloHost()
+    apollo_user = reader.getApolloUser()
+    toolDirectory = reader.getToolDir()
+    jbrowse_hub = reader.getJBrowseHubDir()
+
+    logging.info("#### JBrowseArchiveCreator: Start to upload JBrowse Hub to Apollo instance: %s #### ", apollo_host)
+
+    # Set up apollo
+    apollo = ApolloInstance(apollo_host, toolDirectory, user_email)
+    jbrowse_hub_dir = _getHubDir(jbrowse_hub, extra_files_path, genome_name)
+    apollo.loadHubToApollo(apollo_user, species_name, jbrowse_hub_dir, admin=True)
+    outHtml(outputFile, apollo_host, species_name)
+
+    logging.info('#### JBrowseArchiveCreator: Congratulation! JBrowse Hub is uploaded! ####\n')
+    
+def _getHubDir(outputFile, extra_files_path, genome_name):
+    file_dir = os.path.abspath(outputFile)
+    source_dir = os.path.dirname(file_dir)
+    output_folder_name = os.path.basename(extra_files_path)
+    jbrowse_hub_dir = os.path.join(source_dir, output_folder_name, 'myHub', genome_name)
+    return jbrowse_hub_dir
+
+def outHtml(outputFile, host_name, species_name):
+    with open(outputFile, 'w') as htmlfile:
+        htmlstr = 'The new Organism "%s" is created on Apollo: <br>' % species_name
+        jbrowse_hub = '<li><a href = "%s" target="_blank">View JBrowse Hub on Apollo</a></li>' % host_name
+        htmlstr += jbrowse_hub
+        htmlfile.write(htmlstr)     
+
+if __name__ == "__main__":
+    main(sys.argv)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jbrowsehubToApollo.xml	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,93 @@
+<tool id="jbrowsehubtoapollo" name="JBrowseHub To Apollo" version="1.0.0">
+    <description>
+        This Galaxy tool is used to prepare your files to be ready for displaying on JBrowse with Apollo plugin
+    </description>
+
+    <requirements>
+        <requirement type="package" version="3.0.3">apollo_api</requirement>
+    </requirements>
+
+    <stdio>
+    </stdio>
+
+    <command detect_errors="exit_code"><![CDATA[
+        #import json
+        #set global data_parameter_dict = {}
+        #silent $data_parameter_dict.update({"jbrowse_hub": str($jbrowse_hub)})
+        #silent $data_parameter_dict.update({"genome_name": str($genome_name)})
+        #silent $data_parameter_dict.update({"apollo_host": str($apollo_host)})
+        #silent $data_parameter_dict.update({"user_email": str($__user_email__)})
+        #silent $data_parameter_dict.update({"tool_directory": str($__tool_directory__)})
+        #silent $data_parameter_dict.update({"extra_files_path": str($jbrowsehub.extra_files_path)})
+        #if $apollo_users_settings.apollo_users_selector == "yes":
+            #set apollo_user = {"firstname": str($apollo_users_settings.firstname), "lastname": str($apollo_users_settings.lastname), "password": str($apollo_users_settings.password), "user_email": str($apollo_users_settings.user_email)}
+            $data_parameter_dict.update({"apollo_user": $apollo_user})
+        #end if 
+        #set input_data = json.dumps($data_parameter_dict)
+        python $__tool_directory__/jbrowsehubToApollo.py --data_json $input_data -o $output
+    ]]></command>
+
+    <inputs>
+        <param 
+                format="jbrowsehub" 
+                type="data"
+                name="jbrowse_hub" 
+                label="JBrowse Hub created by JBrowse Archive Creator" 
+        />
+        <param
+                name="genome_name"
+                type="text"
+                size="30"
+                value="unknown"
+                label="Species name"
+        />
+        <param
+                name="apollo_host"
+                type="text"
+                label="Apollo host"
+        />
+        <conditional name="apollo_users_settings">
+            <param name="apollo_users_selector" type="select" label="Create or specify your Apollo account">
+                <option value="no" selected="true">Use exist demo user account (will use your galaxy email address for apollo, password: gonramp) </option>
+                <option value="yes">Create or use your own Apollo account</option>
+            </param>
+            <!-- TODO: Avoid redundancy here -->
+            <when value="yes">
+                <param
+                    name="firstname"
+                    type="text"
+                    label="First Name"
+                />
+                <param
+                    name="lastname"
+                    type="text"
+                    label="Last Name"
+                />
+                <param
+                    name="user_email"
+                    type="text"
+                    label="Email Address"
+                />
+                <param
+                    name="password"
+                    type="text"
+                    label="Password"
+                />
+            </when>
+            <when value="no">
+                <param name="default_user" type="hidden"
+                       value="false">
+                </param>
+            </when>
+        </conditional>
+    </inputs>
+    <outputs>
+        <data format="html" name="output" label="${tool.name}" />
+    </outputs>
+
+    <help>
+        This Galaxy tool will load a jbrowse hub to Apollo instance for visualization and interactive annotation.
+    </help>
+    <citations>
+    </citations>
+</tool>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/apollo-arrow.yml	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,10 @@
+## Apollo's Arrow: Global Configuration File.
+# Each stanza should contian a single galaxy server to control.
+#
+# You can set the key __default to the name of a default instance
+__default: local
+
+local:
+    url: ${apollo_host}
+    username: ${admin_user}
+    password: ${admin_pw}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/custom_track_styles.css	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,9 @@
+.${label},
+.plus-${label},
+.minus-${label}
+{
+    background-color: ${color};
+    height: 90%;
+    top: 5%;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tool_dependencies.xml	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<tool_dependency>
+    
+    <package name="apollo_api" version="3.0.3">
+        <install version="1.0">
+            <actions>
+                <action type="download_by_url">https://github.com/galaxy-genome-annotation/python-apollo/archive/3.0.3.tar.gz</action>
+                <action type="make_directory">$INSTALL_DIR/apollo</action>
+                <action type="shell_command">
+                    export PYTHONPATH=$PYTHONPATH:$INSTALL_DIR/apollo &amp;&amp; 
+                    python setup.py install --install-lib $INSTALL_DIR/apollo
+                </action>
+                <action type="set_environment">
+                    <environment_variable action="append_to" name="PYTHONPATH">$INSTALL_DIR/apollo</environment_variable>
+                    <environment_variable action="append_to" name="PATH">$INSTALL_DIR/apollo</environment_variable>
+                </action>
+            </actions>
+        </install>
+    </package>
+
+
+    
+</tool_dependency>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/Reader.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,95 @@
+import json
+import re
+import logging
+import codecs
+from apollo.ApolloUser import ApolloUser
+from util import santitizer 
+
+class Reader(object):
+
+    def __init__(self, input_json_file):
+        self.inputFile = input_json_file
+        self.args = self.loadJson()
+        
+    
+    def loadJson(self):
+        try:
+            data_file = codecs.open(self.inputFile, 'r', 'utf-8')   
+            return json.load(data_file) 
+        except IOError:
+            print "Cannot find JSON file\n"
+            exit(1)
+
+    def getJBrowseHubDir(self):
+        try:
+            return self.args["jbrowse_hub"]
+        except KeyError:
+            print ("jbrowse_hub is not defined in the input file!")
+            exit(1)
+
+    def getToolDir(self):
+        try:
+            return self.args["tool_directory"]
+        except KeyError:
+            print ("tool_directory is not defined in the input file!")
+            exit(1)
+
+    def getExtFilesPath(self):
+        try:
+            return self.args["extra_files_path"]
+        except KeyError:
+            print ("extra_files_path is not defined in the input file!")
+            exit(1)
+
+    def getUserEmail(self):
+        try:
+            return self.args["user_email"]
+        except KeyError:
+            print ("user_email is not defined in the input file!")
+            exit(1)
+    
+    def getDebugMode(self):
+        try:
+            return self.args["debug_mode"]
+        except KeyError:
+            print ("debug_mode is not defined in the input file!")
+            exit(1)
+    
+    def getApolloHost(self):
+        apollo_host = self.args.get("apollo_host")
+        return apollo_host
+        
+        
+    def getSpeciesName(self):
+        species_name = santitizer.sanitize_name_input(self.args["species_name"])
+        return species_name 
+        
+    def getGenomeName(self):
+        jbrowse_hub = self.getJBrowseHubDir()
+        with open(jbrowse_hub, 'r') as f:
+            html = f.read()
+        m = re.search('The new Organism "(.+?)" is created on Apollo', html)
+        if m:
+            genome_name = m.group(1)
+        else:
+            print("Cannot find genome name in the jbrowse hub file!")
+            exit(1)
+        return genome_name
+            
+
+    def getApolloUser(self):
+        user_info = self.args.get("apollo_user")
+        if not user_info:
+            firstname = "demo"
+            lastname = "user"
+            password = "gonramp"
+            user_email = self.getUserEmail()
+        else:
+            firstname = user_info['firstname']
+            lastname = user_info['lastname']
+            user_email = user_info['user_email']
+            password = user_info['password']
+        apollo_user = ApolloUser(user_email, firstname, lastname, password)
+        return apollo_user
+
+    
\ No newline at end of file
Binary file util/Reader.pyc has changed
Binary file util/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/santitizer.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+
+"""
+This class handles the subprocess calls of the different tools used
+in HubArchiveCreator
+"""
+
+import logging
+import os
+import subprocess
+import sys
+import string
+import tempfile
+
+    
+def prefixTrackName(filename):       
+    """
+    santitize trackName. Because track name must begin with a letter and
+    contain only the following chars: [a-zA-Z0-9_].
+    See the "track" Common settings at:
+    https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html#bigPsl_-_Pairwise_Alignments
+    skip the santitization for cytoBandIdeo track
+    """
+    if filename == 'cytoBandIdeo':
+        return filename
+    valid_chars = "_%s%s" % (string.ascii_letters, string.digits)
+    sanitize_name = ''.join([c if c in valid_chars else '_' for c in filename])
+    sanitize_name = "gonramp_" + sanitize_name
+    return sanitize_name
+
+def sanitize_name_input(string_to_sanitize):
+    """
+    Sanitize the string passed in parameter by replacing '/' and ' ' by '_'
+
+    :param string_to_sanitize:
+    :return :
+
+    :Example:
+
+    >>> sanitize_name_input('this/is an//example')
+    this_is_an__example
+    """
+    return string_to_sanitize \
+            .replace("/", "_") \
+            .replace(" ", "_")
+
+def sanitize_name_inputs(inputs_data):
+    """
+    Sanitize value of the keys "name" of the dictionary passed in parameter.
+
+    Because sometimes output from Galaxy, or even just file name, from user inputs, have spaces.
+    Also, it can contain '/' character and could break the use of os.path function.
+
+    :param inputs_data: dict[string, dict[string, string]]
+    """
+    for key in inputs_data:
+        inputs_data[key]["name"] = sanitize_name_input(inputs_data[key]["name"])
+
+def sanitize_group_name(group_name):
+    return group_name.lower().replace(' ', '_')
+
+def sanitize_name(input_name):
+    """
+    Galaxy will name all the files and dirs as *.dat, 
+    the function can replace '.' to '_' for the dirs
+    """
+    validChars = "_-%s%s" % (string.ascii_letters, string.digits)
+    sanitized_name = ''.join([c if c in validChars else '_' for c in input_name])
+    return "gonramp_" + sanitized_name
Binary file util/santitizer.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/subtools.py	Tue Oct 17 17:28:05 2017 -0400
@@ -0,0 +1,379 @@
+#!/usr/bin/env python
+
+"""
+This file include common used functions for converting file format to gff3
+"""
+from collections import OrderedDict
+import json
+import subprocess
+import os
+import sys
+import tempfile
+import string
+import logging
+
+class PopenError(Exception):
+    def __init__(self, cmd, error, return_code):
+        self.cmd = cmd
+        self.error = error
+        self.return_code = return_code
+
+    def __str__(self):
+        message = "The subprocess {0} has returned the error: {1}.".format(
+            self.cmd, self.return_code)
+        message = ','.join(
+            (message, "Its error message is: {0}".format(self.error)))
+        return repr(message)
+
+
+def _handleExceptionAndCheckCall(array_call, **kwargs):
+    """
+    This class handle exceptions and call the tool.
+    It maps the signature of subprocess.check_call:
+    See https://docs.python.org/2/library/subprocess.html#subprocess.check_call
+    """
+    stdout = kwargs.get('stdout', subprocess.PIPE)
+    stderr = kwargs.get('stderr', subprocess.PIPE)
+    shell = kwargs.get('shell', False)
+    stdin = kwargs.get('stdin', None)
+
+    cmd = array_call[0]
+
+    output = None
+    error = None
+
+    # TODO: Check the value of array_call and <=[0]
+    logging.debug("Calling {0}:".format(cmd))
+    logging.debug("%s", array_call)
+    logging.debug("---------")
+
+    # TODO: Use universal_newlines option from Popen?
+    try:
+        p = subprocess.Popen(array_call, stdout=stdout,
+                             stderr=stderr, shell=shell, stdin=stdin)
+
+        # TODO: Change this because of possible memory issues => https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate
+
+        output, error = p.communicate()
+
+        if stdout == subprocess.PIPE:
+            logging.debug("\t{0}".format(output))
+        else:
+            logging.debug("\tOutput in file {0}".format(stdout.name))
+        # If we detect an error from the subprocess, then we raise an exception
+        # TODO: Manage if we raise an exception for everything, or use CRITICAL etc... but not stop process
+        # TODO: The responsability of returning a sys.exit() should not be there, but up in the app.
+        if p.returncode:
+            if stderr == subprocess.PIPE:
+                raise PopenError(cmd, error, p.returncode)
+            else:
+                # TODO: To Handle properly with a design behind, if we received a option as a file for the error
+                raise Exception("Error when calling {0}. Error as been logged in your file {1}. Error code: {2}"
+                                .format(cmd, stderr.name, p.returncode))
+
+    except OSError as e:
+        message = "The subprocess {0} has encountered an OSError: {1}".format(
+            cmd, e.strerror)
+        if e.filename:
+            message = '\n'.join(
+                (message, ", against this file: {0}".format(e.filename)))
+        logging.error(message)
+        sys.exit(-1)
+    except PopenError as p:
+        message = "The subprocess {0} has returned the error: {1}.".format(
+            p.cmd, p.return_code)
+        message = '\n'.join(
+            (message, "Its error message is: {0}".format(p.error)))
+
+        logging.exception(message)
+
+        sys.exit(p.return_code)
+    except Exception as e:
+        message = "The subprocess {0} has encountered an unknown error: {1}".format(
+            cmd, e)
+        logging.exception(message)
+
+        sys.exit(-1)
+    return p
+
+
+def write_features(field, attribute, gff3):
+    """
+    The function write the features to gff3 format (defined in https://github.com/The-Sequence-Ontology/Specifications/blob/master/gff3.md)
+    field, attribute are ordered dictionary 
+    gff3 is the file handler
+    """
+    attr = []
+    for v in field.values():
+        gff3.write(str(v) + '\t')
+    for k, v in attribute.items():
+        s = str(k) + '=' + str(v)
+        attr.append(s)
+    gff3.write(';'.join(attr))
+    gff3.write('\n')
+
+def twoBitInfo(two_bit_file_name, two_bit_info_file):
+    """
+    Call twoBitInfo and write the result into twoBit_info_file
+    :param two_bit_file_name:
+    :param two_bit_info_file:
+    :return the subprocess.check_call return object:
+    """
+    array_call = ['twoBitInfo', two_bit_file_name, two_bit_info_file]
+    p = _handleExceptionAndCheckCall(array_call)
+    return p
+
+
+def faToTwoBit(fasta_file_name, twoBitFile):
+    """
+    This function call faToTwoBit UCSC tool, and return the twoBitFile
+    :param fasta_file_name:
+    :param mySpecieFolder:
+    :return:
+    """
+
+    array_call = ['faToTwoBit', fasta_file_name, twoBitFile]
+    _handleExceptionAndCheckCall(array_call)
+
+    return twoBitFile
+
+def sortChromSizes(two_bit_info_file_name, chrom_sizes_file_name):
+    """
+    Call sort with -k2rn on two_bit_info_file_name and write the result into chrom_sizes_file_name
+    :param two_bit_info_file_name:
+    :param chrom_sizes_file_name:
+    :return:
+    """
+    array_call = ['sort', '-k2rn', two_bit_info_file_name,
+                  '-o', chrom_sizes_file_name]
+    p = _handleExceptionAndCheckCall(array_call)
+    return p
+
+def getChromSizes(reference, tool_dir):
+    #TODO: find a better way instead of shipping the two exec files with the tool
+    faToTwoBit = os.path.join(tool_dir, 'faToTwoBit')
+    twoBitInfo = os.path.join(tool_dir, 'twoBitInfo')
+    try:
+        twoBitFile = tempfile.NamedTemporaryFile(bufsize=0)
+        chrom_sizes = tempfile.NamedTemporaryFile(bufsize=0, suffix='.chrom.sizes', delete=False)
+    except IOError as err:
+        print "Cannot create tempfile err({0}): {1}".format(err.errno, err.strerror)
+    try:
+        subprocess.call(['faToTwoBit', reference, twoBitFile.name])
+    except OSError as err:
+        print "Cannot generate twoBitFile from faToTwoBit err({0}): {1}".format(err.errno, err.strerror)
+    try:
+        subprocess.call(['twoBitInfo', twoBitFile.name, chrom_sizes.name])
+    except OSError as err:
+        print "Cannot generate chrom_sizes from twoBitInfo err({0}): {1}".format(err.errno, err.strerror)
+    return chrom_sizes
+
+def sequence_region(chrom_sizes):
+    """
+    This function read from a chromatin size file generated by twoBitInfo and write the information to dict
+    return a dict
+    """
+    f = open(chrom_sizes, 'r')
+    sizes = f.readlines()
+    sizes_dict = {}
+    for line in sizes:
+        chrom_info = line.rstrip().split('\t')
+        sizes_dict[chrom_info[0]] = chrom_info[1]
+    return sizes_dict
+
+def child_blocks(parent_field, parent_attr, gff3, child_type):
+    num = 0
+    blockcount = int(parent_attr['blockcount'])
+    chromstart = parent_attr['chromstarts'].split(',')
+    blocksize = parent_attr['blocksizes'].split(',')
+    parent_start = parent_field['start']
+    while num < blockcount:
+        child_attr = OrderedDict()
+        child_field = parent_field
+        child_field['type'] = child_type
+        child_field['start'] = int(chromstart[num]) + int(parent_start)
+        child_field['end'] = int(child_field['start']) + int(blocksize[num]) - 1
+        child_attr['ID'] = parent_attr['ID'] + '_part_' + str(num+1)
+        child_attr['Parent'] = parent_attr['ID']
+        write_features(child_field, child_attr, gff3)
+        num = num + 1
+
+def add_tracks_to_json(trackList_json, new_tracks, modify_type):
+    """
+    Add to track configuration (trackList.json)
+    # modify_type =  'add_tracks': add a new track like bam or bigwig, new_track = dict()
+    # modify_type = 'add_attr': add configuration to the existing track, new_track = dict(track_name: dict())
+    """
+    with open(trackList_json, 'r+') as f:
+        data = json.load(f)
+        if modify_type == 'add_tracks':
+            data['tracks'].append(new_tracks)
+        elif modify_type == 'add_attr':
+            for k in new_tracks:
+                for track in data['tracks']:
+                    if k.lower() in track['urlTemplate'].lower():
+                        attr = new_tracks[k]
+                        for k, v in attr.items():
+                            track[k] = v
+        f.seek(0, 0)
+        f.write(json.dumps(data, separators=(',' , ':'), indent=4))
+        f.truncate()
+        f.close()
+
+
+def createBamIndex(bamfile):
+    subprocess.call(['samtools', 'index', bamfile])
+    filename = bamfile + '.bai'
+    if os.path.exists(filename):
+        return filename
+    else:
+        raise ValueError('Did not find bai file')
+
+def flatfile_to_json(inputFile, dataType, trackType, trackLabel, outputFolder, options=None, compress=False):
+    if "bed" in dataType:
+        fileType = "--bed"
+    elif "gff" in dataType:
+        fileType = "--gff"
+    else:
+        raise ValueError("%s is not a valid filetype for flatfile_to_json" % dataType)
+       
+
+    array_call = ['flatfile-to-json.pl', 
+                   fileType, inputFile, 
+                   '--trackType', trackType, 
+                   '--trackLabel', trackLabel,
+                   '--out', outputFolder]
+    if compress:
+        array_call.append('--compress')
+    if options:
+        config = options.get("config")
+        clientConfig = options.get("clientConfig")
+        renderClassName = options.get('renderClassName')
+        subfeatureClasses = options.get('subfeatureClasses')
+        load_type = options.get("type")
+        if clientConfig:
+            array_call.append('--clientConfig')
+            array_call.append(clientConfig)
+        if config:
+            array_call.append('--config')
+            array_call.append(config)
+        if load_type:
+            array_call.append('--type')
+            array_call.append(load_type)
+        if renderClassName:
+            array_call.append('--renderClassName')
+            array_call.append(renderClassName)
+        if subfeatureClasses:
+            array_call.append('--subfeatureClasses')
+            array_call.append(json.dumps(subfeatureClasses))
+
+    p = _handleExceptionAndCheckCall(array_call)
+    return p
+
+def bam_to_json(inputFile, trackLabel, outputFolder, options=None, compress=False):
+    
+    array_call = ['bam-to-json.pl', 
+                   '--bam', inputFile, 
+                   '--trackLabel', trackLabel,
+                   '--out', outputFolder]
+    if compress:
+        array_call.append('--compress')
+    if options:
+        config = options.get('config')
+        clientConfig = options.get('clientConfig')
+        if clientConfig:
+            array_call.append('--clientConfig')
+            array_call.append(clientConfig)
+        if config:
+            array_call.append('--config')
+            array_call.append(config)
+
+    p = _handleExceptionAndCheckCall(array_call)
+    return p
+
+def add_track_json(trackList, track_json):
+    track_json = json.dumps(track_json)
+    new_track = subprocess.Popen(['echo', track_json], stdout=subprocess.PIPE)
+    p = subprocess.call(['add-track-json.pl', trackList], stdin=new_track.stdout)
+    return p
+
+def prepare_refseqs(fasta_file_name, outputFolder):
+    array_call = ['prepare-refseqs.pl', '--fasta', fasta_file_name, '--out', outputFolder]
+    p = _handleExceptionAndCheckCall(array_call)
+    return p       
+
+def generate_names(outputFolder):
+    array_call = ['generate-names.pl', '-v', '--out', outputFolder]
+    p = _handleExceptionAndCheckCall(array_call)
+    return p  
+   
+def validateFiles(input_file, chrom_sizes_file_name, file_type, options=None):
+    """
+    Call validateFiles on input_file, using chrom_sizes_file_name and file_type
+    :param input_file:
+    :param chrom_sizes_file_name:
+    :param file_type:
+    :return:
+    """
+    
+    array_call = ['validateFiles', '-chromInfo=' + chrom_sizes_file_name, '-type='+ file_type, input_file]
+    if options:
+        tab = options.get("tab")
+        autoSql = options.get("autoSql")
+        logging.debug("tab: {0}".format(tab))
+        logging.debug("autoSql: {0}".format(autoSql))
+        if autoSql:
+            autoSql = ''.join(['-as=', autoSql])
+            array_call.append(autoSql)
+        if tab:
+            array_call.append('-tab')
+    p = _handleExceptionAndCheckCall(array_call)
+    return p
+
+def arrow_add_organism(organism_name, organism_dir, public=False):
+    array_call = ['arrow', 'organisms', 'add_organism', organism_name, organism_dir]
+    if public:
+        array_call.append('--public')
+    print array_call
+    p = subprocess.check_output(array_call)
+    return p
+
+def arrow_create_user(user_email, firstname, lastname, password, admin=False):
+    """ Create a new user of Apollo, the default user_role is "user" """
+    array_call = ['arrow', 'users', 'create_user', user_email, firstname, lastname, password]
+    if admin:
+        array_call += ['--role', 'admin']
+    logging.debug("%s", array_call)
+    print array_call
+    p = subprocess.check_output(array_call)
+    print ("p = %s", p)
+    return p
+
+def arrow_update_organism_permissions(user_id, organism, **user_permissions):
+    array_call = ['arrow', 'users', 'update_organism_permissions', str(user_id), str(organism)]
+    admin = user_permissions.get("admin", False)
+    write = user_permissions.get("write", False)
+    read = user_permissions.get("read", False)
+    export = user_permissions.get("export", False)
+    if admin:
+        array_call.append('--administrate')
+    if write:
+        array_call.append('--write')
+    if read:
+        array_call.append('--read')
+    if export:
+        array_call.append('--export')
+    p = subprocess.check_output(array_call)
+    return p
+
+def arrow_get_users(user_email):
+    array_call = ['arrow', 'users', 'get_users']
+    logging.debug("%s", array_call)
+    print array_call
+    p = subprocess.check_output(array_call)
+    all_users = json.loads(p)
+    for d  in all_users:
+        if d['username'] == user_email:
+            return d['userId']
+    logging.error("Cannot find user %s", user_email)
+
Binary file util/subtools.pyc has changed