This commit is contained in:
Dannon Baker
2012-08-29 10:18:23 -04:00
31 changed files with 2259 additions and 111 deletions
+43 -1
View File
@@ -719,7 +719,49 @@ class LineCount( Text ):
pass
class Newick( Text ):
pass
"""New Hampshire/Newick Format"""
file_ext = "nhx"
MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True )
def __init__(self, **kwd):
"""Initialize foobar datatype"""
Text.__init__(self, **kwd)
def init_meta( self, dataset, copy_from=None ):
Text.init_meta( self, dataset, copy_from=copy_from )
def sniff( self, filename ):
""" Returning false as the newick format is too general and cannot be sniffed."""
return False
class Nexus( Text ):
"""Nexus format as used By Paup, Mr Bayes, etc"""
file_ext = "nex"
MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True )
def __init__(self, **kwd):
"""Initialize foobar datatype"""
Text.__init__(self, **kwd)
def init_meta( self, dataset, copy_from=None ):
Text.init_meta( self, dataset, copy_from=copy_from )
def sniff( self, filename ):
"""All Nexus Files Simply puts a '#NEXUS' in its first line"""
f = open(filename, "r")
firstline = f.readline().upper()
f.close()
if "#NEXUS" in firstline:
return True
else:
return False
# ------------- Utility methods --------------
+15 -16
View File
@@ -6,6 +6,7 @@ import registry
from galaxy import util
from galaxy.datatypes.checkers import *
from galaxy.datatypes.binary import unsniffable_binary_formats
from encodings import search_function as encodings_search_function
log = logging.getLogger(__name__)
@@ -15,7 +16,7 @@ def get_test_fname(fname):
full_path = os.path.join(path, 'test', fname)
return full_path
def stream_to_open_named_file( stream, fd, filename ):
def stream_to_open_named_file( stream, fd, filename, source_encoding=None, source_error='strict', target_encoding=None, target_error='strict' ):
"""Writes a stream to the provided file descriptor, returns the file's name and bool( is_multi_byte ). Closes file descriptor"""
#signature and behavor is somewhat odd, due to backwards compatibility, but this can/should be done better
CHUNK_SIZE = 1048576
@@ -23,6 +24,10 @@ def stream_to_open_named_file( stream, fd, filename ):
is_compressed = False
is_binary = False
is_multi_byte = False
if not target_encoding or not encodings_search_function( target_encoding ):
target_encoding = util.DEFAULT_ENCODING #utf-8
if not source_encoding:
source_encoding = util.DEFAULT_ENCODING #sys.getdefaultencoding() would mimic old behavior (defaults to ascii)
while 1:
chunk = stream.read( CHUNK_SIZE )
if not chunk:
@@ -42,13 +47,12 @@ def stream_to_open_named_file( stream, fd, filename ):
chars = chunk[:100]
is_multi_byte = util.is_multi_byte( chars )
if not is_multi_byte:
for char in chars:
if ord( char ) > 128:
is_binary = True
break
is_binary = util.is_binary( chunk )
data_checked = True
if not is_compressed and not is_binary:
os.write( fd, chunk.encode( "utf-8" ) )
if not isinstance( chunk, unicode ):
chunk = chunk.decode( source_encoding, source_error )
os.write( fd, chunk.encode( target_encoding, target_error ) )
else:
# Compressed files must be encoded after they are uncompressed in the upload utility,
# while binary files should not be encoded at all.
@@ -56,10 +60,10 @@ def stream_to_open_named_file( stream, fd, filename ):
os.close( fd )
return filename, is_multi_byte
def stream_to_file( stream, suffix='', prefix='', dir=None, text=False ):
def stream_to_file( stream, suffix='', prefix='', dir=None, text=False, **kwd ):
"""Writes a stream to a temporary file, returns the temporary file's name"""
fd, temp_name = tempfile.mkstemp( suffix=suffix, prefix=prefix, dir=dir, text=text )
return stream_to_open_named_file( stream, fd, temp_name )
return stream_to_open_named_file( stream, fd, temp_name, **kwd )
def check_newlines( fname, bytes_to_read=52428800 ):
"""
@@ -305,14 +309,9 @@ def guess_ext( fname, sniff_order=None, is_multi_byte=False ):
else:
for hdr in headers:
for char in hdr:
if len( char ) > 1:
for c in char:
if ord( c ) > 128:
is_binary = True
break
elif ord( char ) > 128:
is_binary = True
break
#old behavior had 'char' possibly having length > 1,
#need to determine when/if this occurs
is_binary = util.is_binary( char )
if is_binary:
break
if is_binary:
+21
View File
@@ -76,3 +76,24 @@ class CisML( GenericXml ):
dataset.blurb = 'file purged from disk'
def sniff( self, filename ):
return False
class Phyloxml( GenericXml ):
"""Format for defining phyloxml data http://www.phyloxml.org/"""
file_ext = "phyloxml"
def set_peek( self, dataset, is_multi_byte=False ):
"""Set the peek and blurb text"""
if not dataset.dataset.purged:
dataset.peek = data.get_file_peek( dataset.file_name, is_multi_byte=is_multi_byte )
dataset.blurb = 'Phyloxml data'
else:
dataset.peek = 'file does not exist'
dataset.blurb = 'file purged from disk'
def sniff( self, filename ):
""""Checking for keyword - 'phyloxml' always in lowercase in the first few lines"""
f = open(filename, "r")
firstlines = "".join(f.readlines(5))
f.close()
if "phyloxml" in firstlines:
return True
return False
+10 -4
View File
@@ -471,7 +471,7 @@ class JobWrapper( object ):
job.user.total_disk_usage += bytes
# fix permissions
for path in [ dp.real_path for dp in self.get_output_fnames() ]:
for path in [ dp.real_path for dp in self.get_mutable_output_fnames() ]:
util.umask_fix_perms( path, self.app.config.umask, 0666, self.app.config.gid )
self.sa_session.flush()
log.debug( 'job %d ended' % self.job_id )
@@ -679,6 +679,11 @@ class JobWrapper( object ):
self.compute_outputs()
return self.output_paths
def get_mutable_output_fnames( self ):
if self.output_paths is None:
self.compute_outputs()
return filter( lambda dsp: dsp.mutable, self.output_paths )
def get_output_hdas_and_fnames( self ):
if self.output_hdas_and_paths is None:
self.compute_outputs()
@@ -686,10 +691,11 @@ class JobWrapper( object ):
def compute_outputs( self ) :
class DatasetPath( object ):
def __init__( self, dataset_id, real_path, false_path = None ):
def __init__( self, dataset_id, real_path, false_path = None, mutable = True ):
self.dataset_id = dataset_id
self.real_path = real_path
self.false_path = false_path
self.mutable = mutable
def __str__( self ):
if self.false_path is None:
return self.real_path
@@ -706,13 +712,13 @@ class JobWrapper( object ):
self.output_hdas_and_paths = {}
for name, hda in [ ( da.name, da.dataset ) for da in job.output_datasets + job.output_library_datasets ]:
false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % hda.dataset.id ) )
dsp = DatasetPath( hda.dataset.id, hda.dataset.file_name, false_path )
dsp = DatasetPath( hda.dataset.id, hda.dataset.file_name, false_path, mutable = hda.dataset.external_filename is None )
self.output_paths.append( dsp )
self.output_hdas_and_paths[name] = hda, dsp
if special:
false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % special.dataset.id ) )
else:
results = [ ( da.name, da.dataset, DatasetPath( da.dataset.dataset.id, da.dataset.file_name ) ) for da in job.output_datasets + job.output_library_datasets ]
results = [ ( da.name, da.dataset, DatasetPath( da.dataset.dataset.id, da.dataset.file_name, mutable = da.dataset.dataset.external_filename is None ) ) for da in job.output_datasets + job.output_library_datasets ]
self.output_paths = [t[2] for t in results]
self.output_hdas_and_paths = dict([(t[0], t[1:]) for t in results])
if special:
+10 -9
View File
@@ -115,15 +115,16 @@ class GenomeTransferPlugin( DataTransfer ):
files = tar.getmembers()
for filename in files:
z = tar.extractfile(filename)
try:
chunk = z.read( CHUNK_SIZE )
except IOError:
os.close( fd )
log.error( 'Problem decompressing compressed data' )
exit()
if not chunk:
break
os.write( fd, chunk )
while 1:
try:
chunk = z.read( CHUNK_SIZE )
except IOError:
os.close( fd )
log.error( 'Problem decompressing compressed data' )
exit()
if not chunk:
break
os.write( fd, chunk )
os.write( fd, '\n' )
os.close( fd )
tar.close()
+1 -1
View File
@@ -95,7 +95,7 @@ class UsesAnnotations:
""" Returns a user's annotation string for an item. """
annotation_obj = self.get_item_annotation_obj( db_session, user, item )
if annotation_obj:
return annotation_obj.annotation
return galaxy.util.unicodify( annotation_obj.annotation )
return None
def get_item_annotation_obj( self, db_session, user, item ):
+10 -7
View File
@@ -187,7 +187,9 @@ class ToolBox( object ):
section.elems[ section_key ] = workflow
log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
elif section_key.startswith( 'label_' ):
section.elems[ section_key ] = section_val
if section_val:
section.elems[ section_key ] = section_val
log.debug( "Loaded label: %s" % ( section_val.text ) )
self.tool_panel[ key ] = section
def load_integrated_tool_panel_keys( self ):
"""
@@ -215,12 +217,12 @@ class ToolBox( object ):
section.elems[ key ] = None
elif section_elem.tag == 'label':
key = 'label_%s' % section_elem.get( 'id' )
section.elems[ key ] = ToolSectionLabel( section_elem )
section.elems[ key ] = None
key = 'section_%s' % elem.get( 'id' )
self.integrated_tool_panel[ key ] = section
elif elem.tag == 'label':
key = 'label_%s' % elem.get( 'id' )
self.integrated_tool_panel[ key ] = ToolSectionLabel( elem )
self.integrated_tool_panel[ key ] = None
def write_integrated_tool_panel_config_file( self ):
"""
Write the current in-memory version of the integrated_tool_panel.xml file to disk. Since Galaxy administrators
@@ -254,10 +256,11 @@ class ToolBox( object ):
if section_item:
os.write( fd, ' <workflow id="%s" />\n' % section_item.id )
elif section_key.startswith( 'label_' ):
label_id = section_item.id or ''
label_text = section_item.text or ''
label_version = section_item.version or ''
os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
if section_item:
label_id = section_item.id or ''
label_text = section_item.text or ''
label_version = section_item.version or ''
os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
os.write( fd, ' </section>\n' )
os.write( fd, '</toolbox>\n' )
os.close( fd )
@@ -54,6 +54,7 @@ class ManagedIndexer():
self._log( self.locations )
self._log( 'Indexer %s completed successfully.' % indexer )
self._flush_files()
exit(0)
def _check_link( self ):
self._log( 'Checking symlink to %s' % self.fafile )
+42 -2
View File
@@ -34,6 +34,9 @@ _lock = threading.RLock()
gzip_magic = '\037\213'
bz2_magic = 'BZh'
DEFAULT_ENCODING = 'utf-8'
NULL_CHAR = '\000'
BINARY_CHARS = [ NULL_CHAR ]
from inflection import Inflector, English
inflector = Inflector(English)
@@ -57,6 +60,32 @@ def is_multi_byte( chars ):
return True
return False
def is_binary( value, binary_chars=None ):
"""
File is binary if it contains a null-byte by default (e.g. behavior of grep, etc.).
This may fail for utf-16 files, but so would ASCII encoding.
>>> is_binary( string.printable )
False
>>> is_binary( '\\xce\\x94' )
False
>>> is_binary( '\\000' )
True
"""
if binary_chars is None:
binary_chars = BINARY_CHARS
for binary_char in binary_chars:
if binary_char in value:
return True
return False
def get_charset_from_http_headers( headers, default=None ):
rval = headers.get('content-type', None )
if rval and 'charset=' in rval:
rval = rval.split('charset=')[-1].split(';')[0].strip()
if rval:
return rval
return default
def synchronized(func):
"""This wrapper will serialize access to 'func' to a single thread. Use it as a decorator."""
def caller(*params, **kparams):
@@ -333,6 +362,17 @@ def roundify(amount, sfs = 2):
else:
return amount[0:sfs] + '0'*(len(amount) - sfs)
def unicodify( value, encoding=DEFAULT_ENCODING, error='replace', default=None ):
"""
Returns a unicode string or None
"""
if isinstance( value, unicode ):
return value
try:
return unicode( value, encoding, error )
except:
return default
def object_to_string( obj ):
return binascii.hexlify( pickle.dumps( obj, 2 ) )
@@ -502,7 +542,7 @@ def stringify_dictionary_keys( in_dict ):
def recursively_stringify_dictionary_keys( d ):
if isinstance(d, dict):
return dict([(k.encode('utf-8'), recursively_stringify_dictionary_keys(v)) for k,v in d.iteritems()])
return dict([(k.encode( DEFAULT_ENCODING ), recursively_stringify_dictionary_keys(v)) for k,v in d.iteritems()])
elif isinstance(d, list):
return [recursively_stringify_dictionary_keys(x) for x in d]
else:
@@ -622,7 +662,7 @@ def send_mail( frm, to, subject, body, config ):
Sends an email.
"""
to = listify( to )
msg = MIMEText( body )
msg = MIMEText( body.encode( 'ascii', 'replace' ) )
msg[ 'To' ] = ', '.join( to )
msg[ 'From' ] = frm
msg[ 'Subject' ] = subject
+9 -4
View File
@@ -454,7 +454,7 @@ def create_tool_dependency_objects( app, tool_shed_repository, relative_install_
def generate_clone_url( trans, repository ):
"""Generate the URL for cloning a repository."""
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
return '%s/repos/%s/%s' % ( tool_shed_url, repository.owner, repository.name )
return url_join( tool_shed_url, 'repos', repository.owner, repository.name )
def generate_datatypes_metadata( datatypes_config, metadata_dict ):
"""Update the received metadata_dict with information from the parsed datatypes_config."""
tree = ElementTree.parse( datatypes_config )
@@ -993,7 +993,7 @@ def get_converter_and_display_paths( registration_elem, relative_install_dir ):
break
return converter_path, display_path
def get_ctx_rev( tool_shed_url, name, owner, changeset_revision ):
url = '%s/repository/get_ctx_rev?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % ( tool_shed_url, name, owner, changeset_revision )
url = url_join( tool_shed_url, 'repository/get_ctx_rev?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % ( name, owner, changeset_revision ) )
response = urllib2.urlopen( url )
ctx_rev = response.read()
response.close()
@@ -1221,8 +1221,8 @@ def get_tool_version_association( app, parent_tool_version, tool_version ):
def get_update_to_changeset_revision_and_ctx_rev( trans, repository ):
"""Return the changeset revision hash to which the repository can be updated."""
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
url = '%s/repository/get_changeset_revision_and_ctx_rev?name=%s&owner=%s&changeset_revision=%s' % \
( tool_shed_url, repository.name, repository.owner, repository.installed_changeset_revision )
url = url_join( tool_shed_url, 'repository/get_changeset_revision_and_ctx_rev?name=%s&owner=%s&changeset_revision=%s' % \
( repository.name, repository.owner, repository.installed_changeset_revision ) )
try:
response = urllib2.urlopen( url )
encoded_update_dict = response.read()
@@ -1645,3 +1645,8 @@ def update_tool_shed_repository_status( app, tool_shed_repository, status ):
tool_shed_repository.status = status
sa_session.add( tool_shed_repository )
sa_session.flush()
def url_join( *args ):
parts = []
for arg in args:
parts.append( arg.strip( '/' ) )
return '/'.join( parts )
@@ -0,0 +1 @@
__author__ = 'Tomithy'
@@ -0,0 +1,125 @@
import json
class Node(object):
"""Node class of PhyloTree, which represents a CLAUDE in a phylogenetic tree"""
def __init__(self, nodeName, **kwargs):
"""Creates a node and adds in the typical annotations"""
self.name, self.id = nodeName, kwargs.get("id", 0)
self.depth = kwargs.get("depth", 0)
self.children = []
self.isInternal = kwargs.get("isInternal", 0)
self.length, self.bootstrap = kwargs.get("length", 0), kwargs.get("bootstrap", None)
self.events = kwargs.get("events", "")
# clean up boot strap values
if self.bootstrap == -1:
self.bootstrap = None
def addChildNode(self, child):
"""Adds a child node to the current node"""
if isinstance(child, Node):
self.children.append(child)
else:
self.children += child
def __str__(self):
return self.name + " id:" + str(self.id) + ", depth: " + str(self.depth)
def toJson(self):
"""Converts the data in the node to a dict representation of json"""
thisJson = {
"name" : self.name,
"id" : self.id,
"depth" : self.depth,
"dist" : self.length
}
thisJson = self.addChildrenToJson(thisJson)
thisJson = self.addMiscToJson(thisJson)
return thisJson
def addChildrenToJson(self, jsonDict):
"""Needs a special method to addChildren, such that the key does not appear in the Jsondict when the children is empty
this requirement is due to the layout algorithm used by d3 layout for hiding subtree """
if len(self.children) > 0:
children = [ node.toJson() for node in self.children]
jsonDict["children"] = children
return jsonDict
def addMiscToJson(self, jsonDict):
"""Adds other misc attributes to json if they are present"""
if not self.events == "":
jsonDict["events"] = self.events
if not self.bootstrap == None:
jsonDict["bootstrap"] = self.bootstrap
return jsonDict
class PhyloTree(object):
"""Standardized python based class to represent the phylogenetic tree parsed from different
phylogenetic file formats."""
def __init__(self):
self.root, self.rootAttr = None, {}
self.nodes = {}
self.title = None
self.id = 1
def addAttributesToRoot(self, attrDict):
"""Adds attributes to root, but first we put it in a temp store and bind it with root when .toJson is called"""
for key, value in attrDict.items():
self.rootAttr[key] = value
def makeNode(self, nodeName, **kwargs):
"""Called to make a node within PhyloTree, arbitrary kwargs can be passed to annotate nodes
Tracks the number of nodes via internally incremented id"""
kwargs["id"] = self.id
self.id += 1
return Node(nodeName, **kwargs)
def addRoot(self, root):
"""Creates a root for phyloTree"""
assert isinstance(root, Node)
root.parent = None
self.root = root
def generateJsonableDict(self):
"""Changes itself into a dictonary by recurssively calling the tojson on all its nodes. Think of it
as a dict in an array of dict in an array of dict and so on..."""
jsonTree = ""
if self.root:
assert isinstance(self.root, Node)
jsonTree = self.root.toJson()
for key, value in self.rootAttr.items():
# transfer temporary stored attr to root
jsonTree[key] = value
else:
raise Exception("Root is not assigned!")
return jsonTree
class Base_Parser(object):
"""Base parsers contain all the methods to handle phylogeny tree creation and
converting the data to json that all parsers should have"""
def __init__(self):
self.phyloTrees = []
def parseFile(self, filePath):
"""Base method that all phylogeny file parser should have"""
raise Exception("Base method for phylogeny file parsers is not implemented")
def toJson(self, jsonDict):
"""Convenience method to get a json string from a python json dict"""
return json.dumps(jsonDict)
def _writeJsonToFile(self, filepath, json):
"""Writes the file out to the system"""
f = open(filepath, "w")
f.writelines(json)
f.close()
@@ -0,0 +1,185 @@
from baseparser import Base_Parser, PhyloTree
import re
class Newick_Parser(Base_Parser):
"""For parsing trees stored in the newick format (.nhx)
It is necessarily more complex because this parser is later extended by Nexus for parsing newick as well.."""
def __init__(self):
super(Newick_Parser, self).__init__()
def parseFile(self, filePath):
"""Parses a newick file to obtain the string inside. Returns: jsonableDict"""
with open(filePath, "r") as newickFile:
newickString = newickFile.read()
newickString = newickString.replace("\n", "").replace("\r", "")
return [self.parseData(newickString)], "Success"
def parseData(self, newickString):
"""To be called on a newickString directly to parse it. Returns: jsonableDict"""
return self._parseNewickToJson(newickString)
def _parseNewickToJson(self, newickString, treeName=None, nameMap=None):
"""parses a newick representation of a tree into a PhyloTree data structure,
which can be easily converted to json"""
self.phyloTree = PhyloTree()
newickString = self.cleanNewickString(newickString)
if nameMap:
newickString = self._mapName(newickString, nameMap)
self.phyloTree.root = self.parseNode(newickString, 0)
if nameMap:
self.phyloTree.addAttributesToRoot({"treeName": treeName})
return self.phyloTree.generateJsonableDict()
def cleanNewickString(self, rawNewick):
"""removing semi colon, and illegal json characters (\,',") and white spaces"""
return re.sub(r'\s|;|\"|\'|\\', '', rawNewick)
def _makeNodesFromString(self, string, depth):
"""elements separated by comma could be empty"""
if string.find("(") != -1:
raise Exception("Tree is not well form, location: " + string)
childrenString = string.split(",")
childrenNodes = []
for childString in childrenString:
if len(childString) == 0:
continue
nodeInfo = childString.split(":")
name, length, bootstrap = "", None, -1
if len(nodeInfo) == 2: # has length info
length = nodeInfo[1]
# checking for bootstap values
name = nodeInfo[0]
try: # Nexus may bootstrap in names position
name = float(name)
if 0<= name <= 1:
bootstrap = name
elif 1 <= name <= 100:
bootstrap = name / 100
name = ""
except ValueError:
name = nodeInfo[0]
else:
name = nodeInfo[0] # string only contains name
node = self.phyloTree.makeNode(name, length=length, depth=depth, bootstrap= bootstrap)
childrenNodes += [node]
return childrenNodes
def _mapName(self, newickString, nameMap):
"""
Necessary to replace names of terms inside nexus representation
Also, its here because Mailaud's doesnt deal with id_strings outside of quotes(" ")
"""
newString = ""
start = 0
end = 0
for i in xrange(len(newickString)):
if newickString[i] == "(" or newickString[i] == ",":
if re.match(r"[,(]", newickString[i+1:]):
continue
else:
end = i + 1
# i now refers to the starting position of the term to be replaced,
# we will next find j which is the ending pos of the term
for j in xrange(i+1, len(newickString)):
enclosingSymbol = newickString[j] # the immediate symbol after a common or left bracket which denotes the end of a term
if enclosingSymbol == ")" or enclosingSymbol == ":" or enclosingSymbol == ",":
termToReplace = newickString[end:j]
newString += newickString[start : end] + nameMap[termToReplace] #+ "'" "'" +
start = j
break
newString += newickString[start:]
return newString
def parseNode(self, string, depth):
""" Recursive method for parsing newick string, works by stripping down the string into substring
of newick contained with brackers, which is used to call itself.
Eg ... ( A, B, (D, E)C, F, G ) ...
We will make the preceeding nodes first A, B, then the internal node C, its children D, E,
and finally the succeeding nodes F, G"""
# Base case where there is only an empty string
if string == "":
return
# Base case there its only an internal claude
if string.find("(") == -1:
return self._makeNodesFromString(string, depth)
nodes, children = [], [] # nodes refer to the nodes on this level, children refers to the child of the
start = 0
lenOfPreceedingInternalNodeString = 0
bracketStack = []
for j in xrange(len(string)):
if string[j] == "(": #finding the positions of all the open brackets
bracketStack.append(j)
continue
if string[j] == ")": #finding the positions of all the closed brackets to extract claude
i = bracketStack.pop()
if len(bracketStack) == 0: # is child of current node
InternalNode = None
#First flat call to make nodes of the same depth but from the preceeding string.
startSubstring = string[start + lenOfPreceedingInternalNodeString: i]
preceedingNodes = self._makeNodesFromString(startSubstring, depth)
nodes += preceedingNodes
# Then We will try to see if the substring has any internal nodes first, make it then make nodes preceeding it and succeeding it.
if j + 1 < len(string):
stringRightOfBracket = string[j+1:] # Eg. '(b:0.4,a:0.3)c:0.3, stringRightOfBracket = c:0.3
match = re.search(r"[\)\,\(]", stringRightOfBracket)
if match:
indexOfNextSymbol = match.start()
stringRepOfInternalNode = stringRightOfBracket[:indexOfNextSymbol]
internalNodes = self._makeNodesFromString( stringRepOfInternalNode, depth)
if len(internalNodes) > 0:
InternalNode = internalNodes[0]
lenOfPreceedingInternalNodeString = len(stringRepOfInternalNode)
else: # sometimes the node can be the last element of a string
InternalNode = self._makeNodesFromString(string[j+1:], depth)[0]
lenOfPreceedingInternalNodeString = len(string) - j
if InternalNode == None: #creating a generic node if it is unnamed
InternalNode = self.phyloTree.makeNode( "", depth=depth, isInternal=True ) #"internal-" + str(depth)
lenOfPreceedingInternalNodeString = 0
# recussive call to make the internal claude
childSubString = string[ i + 1 : j ]
InternalNode.addChildNode(self.parseNode(childSubString, depth + 1))
nodes.append(InternalNode) # we append the internal node later to preserve order
start = j + 1
continue
if depth == 0: # if its the root node, we do nothing about it and return
return nodes[0]
# Adding last most set of children
endString = string[start:]
if string[start-1] == ")": # if the symbol belongs to an internal node which is created previously, then we remove it from the string left to parse
match = re.search(r"[\)\,\(]", endString)
if match:
endOfNodeName = start + match.start() + 1
endString = string[endOfNodeName:]
nodes += self._makeNodesFromString(endString, depth)
return nodes
@@ -0,0 +1,107 @@
from newickparser import Newick_Parser
import re
MAX_READLINES = 200000
class Nexus_Parser(Newick_Parser):
def __init__(self):
super(Nexus_Parser, self).__init__()
def parseFile(self, filePath):
"""passes a file and extracts its Nexus content."""
return self.parseNexus(filePath)
def parseNexus(self, filename):
""" Nexus data is stored in blocks between a line starting with begin and another line starting with end;
Commends inside square brackets are to be ignored,
For more information: http://wiki.christophchamp.com/index.php/NEXUS_file_format
Nexus can store multiple trees
"""
with open( filename, "rt") as nex_file:
nexlines = nex_file.readlines()
rowCount = 0
inTreeBlock = False # sentinel to check if we are in a tree block
intranslateBlock = False # sentinel to check if we are in the translate region of the tree. Stores synonyms of the labellings
self.inCommentBlock = False
self.nameMapping = None # stores mapping representation used in nexus format
treeNames = []
for line in nexlines:
line = line.replace(";\n", "")
lline = line.lower()
if rowCount > MAX_READLINES or (not nex_file) :
break
rowCount +=1
# We are only interested in the tree block.
if "begin" in lline and "tree" in lline and not inTreeBlock:
inTreeBlock = True
continue
if inTreeBlock and "end" in lline[:3]:
inTreeBlock, currPhyloTree = False, None
continue
if inTreeBlock:
if "title" in lline: # Adding title to the tree
titleLoc = lline.find("title")
title = line[titleLoc + 5:].replace(" ", "")
continue
if "translate" in lline:
intranslateBlock = True
self.nameMapping = {}
continue
if intranslateBlock:
mappingLine = self.splitLinebyWhitespaces(line)
key, value = mappingLine[1], mappingLine[2].replace(",", "").replace("'","") #replacing illegal json characters
self.nameMapping[key] = value
# Extracting newick Trees
if "tree" in lline:
intranslateBlock = False
treeLineCols = self.splitLinebyWhitespaces(line)
treeName, newick = treeLineCols[2], treeLineCols[-1]
if newick == "": # Empty lines can be found in tree blocks
continue
currPhyloTree = self._parseNewickToJson(newick, treeName, nameMap=self.nameMapping)
self.phyloTrees.append(currPhyloTree)
treeIndex = len(self.phyloTrees) - 1
treeNames.append( (treeName, treeIndex) ) # appending name of tree, and its index
continue
return self.phyloTrees, treeNames
def splitLinebyWhitespaces(self, line):
"""replace tabs and write spaces to a single write space, so we can properly split it."""
return re.split(r"\s+", line)
def checkComments(self, line):
"""Check to see if the line/lines is a comment."""
if not self.inCommentBlock:
if "[" in line:
if "]" not in line:
self.inCommentBlock = True
else:
return "Nextline" # need to move on to the nextline after getting out of comment
else :
if "]" in line:
if line.rfind("[") > line.rfind("]"):
pass # a comment block is closed but another is open.
else:
self.inCommentBlock = False
return "Nextline" # need to move on to the nextline after getting out of comment
return ""
@@ -0,0 +1,35 @@
from newickparser import Newick_Parser
from nexusparser import Nexus_Parser
from phyloxmlparser import Phyloxml_Parser
class Phyloviz_DataProvider(object):
def __init__(self):
pass
def parseFile(self, filepath, fileExt):
"""returns [trees], meta
Trees are actually an array of JsonDicts. It's usually one tree, except in the case of Nexus
"""
jsonDicts, meta = [], {}
try:
if fileExt == "nhx": # parses newick files
newickParser = Newick_Parser()
jsonDicts, parseMsg = newickParser.parseFile(filepath)
elif fileExt == "phyloxml": # parses phyloXML files
phyloxmlParser = Phyloxml_Parser()
jsonDicts, parseMsg = phyloxmlParser.parseFile(filepath)
elif fileExt == "nex": # parses nexus files
nexusParser = Nexus_Parser()
jsonDicts, parseMsg = nexusParser.parseFile(filepath)
meta["trees"] = parseMsg
else:
raise Exception("File type is not supported")
meta["msg"] = parseMsg
except Exception:
jsonDicts, meta["msg"] = [], "Parse failed"
return jsonDicts, meta
@@ -0,0 +1,134 @@
from baseparser import Base_Parser, PhyloTree, Node
from xml.etree import ElementTree
class Phyloxml_Parser(Base_Parser):
"""Parses a phyloxml file into a json file that will be passed to PhyloViz for display"""
def __init__(self):
super(Phyloxml_Parser, self).__init__()
self.phyloTree = PhyloTree()
self.tagsOfInterest = {
"clade": "",
"name" : "name",
"branch_length" : "length",
"confidence" : "bootstrap",
"events" : "events"
}
def parseFile(self, filePath):
"""passes a file and extracts its Phylogeny Tree content."""
phyloXmlFile = open(filePath, "r")
xmlTree = ElementTree.parse(phyloXmlFile)
xmlRoot = xmlTree.getroot()[0]
self.nameSpaceIndex = xmlRoot.tag.rfind("}") + 1 # used later by the clean tag method to remove the name space in every element.tag
phyloRoot = None
for child in xmlRoot:
childTag = self.cleanTag(child.tag)
if childTag == "clade":
phyloRoot = child
elif childTag == "name":
self.phyloTree.title = child.text
self.phyloTree.root = self.parseNode(phyloRoot, 0)
jsonDict = self.phyloTree.generateJsonableDict()
return [jsonDict], "Success"
def parseNode(self, node, depth):
"""Parses any node within a phyloxml tree and looks out for claude, which signals the creation of
nodes - internal OR leaf"""
assert isinstance(node, etree._Element)
tag = self.cleanTag(node.tag)
if not tag == "clade":
return None
hasInnerClade = False
# peeking once for parent and once for child to check if the node is internal
for child in node:
childTag = self.cleanTag(child.tag)
if childTag == "clade":
hasInnerClade = True
break
if hasInnerClade: # this node is an internal node
currentNode = self._makeInternalNode(node, depth= depth)
for child in node:
child = self.parseNode(child, depth + 1)
if isinstance(child, Node):
currentNode.addChildNode(child)
else: # this node is a leaf node
currentNode = self._makeLeafNode(node, depth=depth+1)
return currentNode
def _makeLeafNode(self, leafNode, depth = 0 ):
"""Makes leaf nodes by calling Phylotree methods"""
node = {}
for child in leafNode:
childTag = self.cleanTag(child.tag)
if childTag in self.tagsOfInterest:
key = self.tagsOfInterest[childTag] # need to map phyloxml terms to ours
node[key] = child.text
node["depth"] = depth
return self.phyloTree.makeNode(self._getNodeName(leafNode), **node)
def _getNodeName(self, node, depth=-1):
"""Gets the name of a claude. It handles the case where a taxonomy node is involved"""
def getTagFromTaxonomyNode(node):
"""Returns the name of a taxonomy node. A taxonomy node have to be treated differently as the name
is embedded one level deeper"""
phyloxmlTaxoNames = {
"common_name" : "",
"scientific_name" : "",
"code" : ""
}
for child in node:
childTag = self.cleanTag(child.tag)
if childTag in phyloxmlTaxoNames:
return child.text
return ""
nodeName = ""
for child in node:
childTag = self.cleanTag(child.tag)
if childTag == "name" :
nodeName = child.text
break
elif childTag == "taxonomy":
nodeName = getTagFromTaxonomyNode(child)
break
return nodeName
def _makeInternalNode(self, internalNode, depth=0):
""" Makes an internal node from an element object that is guranteed to be a parent node.
Gets the value of interests like events and appends it to a custom node object that will be passed to PhyloTree to make nodes
"""
node = {}
for child in internalNode:
childTag = self.cleanTag(child.tag)
if childTag == "clade":
continue
elif childTag in self.tagsOfInterest:
if childTag == "events": # events is nested 1 more level deeper than others
key, text = "events", self.cleanTag(child[0].tag)
else:
key = self.tagsOfInterest[childTag]
text = child.text
node[key] = text
return self.phyloTree.makeNode(self._getNodeName(internalNode, depth), **node)
def cleanTag(self, tagString):
return tagString[self.nameSpaceIndex:]
+23 -16
View File
@@ -374,7 +374,7 @@ class AdminToolshed( AdminGalaxy ):
def browse_tool_shed( self, trans, **kwd ):
tool_shed_url = kwd[ 'tool_shed_url' ]
galaxy_url = url_for( '/', qualified=True )
url = '%srepository/browse_valid_categories?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
url = url_join( tool_shed_url, 'repository/browse_valid_categories?galaxy_url=%s&webapp=galaxy' % ( galaxy_url ) )
return trans.response.send_redirect( url )
@web.expose
@web.require_admin
@@ -392,8 +392,9 @@ class AdminToolshed( AdminGalaxy ):
# Send a request to the relevant tool shed to see if there are any updates.
repository = get_repository( trans, kwd[ 'id' ] )
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
url = '%s/repository/check_for_updates?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_url, url_for( '/', qualified=True ), repository.name, repository.owner, repository.changeset_revision )
url = url_join( tool_shed_url,
'repository/check_for_updates?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( url_for( '/', qualified=True ), repository.name, repository.owner, repository.changeset_revision ) )
return trans.response.send_redirect( url )
@web.expose
@web.require_admin
@@ -467,14 +468,14 @@ class AdminToolshed( AdminGalaxy ):
def find_tools_in_tool_shed( self, trans, **kwd ):
tool_shed_url = kwd[ 'tool_shed_url' ]
galaxy_url = url_for( '/', qualified=True )
url = '%srepository/find_tools?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
url = url_join( tool_shed_url, 'repository/find_tools?galaxy_url=%s&webapp=galaxy' % galaxy_url )
return trans.response.send_redirect( url )
@web.expose
@web.require_admin
def find_workflows_in_tool_shed( self, trans, **kwd ):
tool_shed_url = kwd[ 'tool_shed_url' ]
galaxy_url = url_for( '/', qualified=True )
url = '%srepository/find_workflows?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
url = url_join( tool_shed_url, 'repository/find_workflows?galaxy_url=%s&webapp=galaxy' % galaxy_url )
return trans.response.send_redirect( url )
def generate_tool_path( self, repository_clone_url, changeset_revision ):
"""
@@ -489,7 +490,7 @@ class AdminToolshed( AdminGalaxy ):
tool_shed_url = items[ 0 ]
repo_path = items[ 1 ]
tool_shed_url = clean_tool_shed_url( tool_shed_url )
return '%s/repos%s/%s' % ( tool_shed_url, repo_path, changeset_revision )
return url_join( tool_shed_url, 'repos', repo_path, changeset_revision )
@web.json
@web.require_admin
def get_file_contents( self, trans, file_path ):
@@ -634,8 +635,9 @@ class AdminToolshed( AdminGalaxy ):
tool_shed_repository,
trans.model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS )
tool_shed_url = get_url_from_repository_tool_shed( trans.app, tool_shed_repository )
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_url, tool_shed_repository.name, tool_shed_repository.owner, tool_shed_repository.changeset_revision )
url = url_join( tool_shed_url,
'/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_repository.name, tool_shed_repository.owner, tool_shed_repository.changeset_revision ) )
response = urllib2.urlopen( url )
text = response.read()
response.close()
@@ -954,7 +956,9 @@ class AdminToolshed( AdminGalaxy ):
repository_ids = kwd.get( 'repository_ids', None )
changeset_revisions = kwd.get( 'changeset_revisions', None )
# Get the information necessary to install each repository.
url = '%srepository/get_repository_information?repository_ids=%s&changeset_revisions=%s&webapp=galaxy' % ( tool_shed_url, repository_ids, changeset_revisions )
url = url_join( tool_shed_url,
'repository/get_repository_information?repository_ids=%s&changeset_revisions=%s&webapp=galaxy' % \
( repository_ids, changeset_revisions ) )
response = urllib2.urlopen( url )
raw_text = response.read()
response.close()
@@ -1097,8 +1101,9 @@ class AdminToolshed( AdminGalaxy ):
name = repo_info_dict.keys()[ 0 ]
repo_info_tuple = repo_info_dict[ name ]
description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, tool_dependencies = repo_info_tuple
url = '%srepository/get_readme?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_url, name, repository_owner, changeset_revision )
url = url_join( tool_shed_url,
'repository/get_readme?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( name, repository_owner, changeset_revision ) )
response = urllib2.urlopen( url )
raw_text = response.read()
response.close()
@@ -1273,8 +1278,9 @@ class AdminToolshed( AdminGalaxy ):
tool_shed = get_tool_shed_from_clone_url( repository_clone_url )
# Get all previous change set revisions from the tool shed for the repository back to, but excluding, the previous valid changeset
# revision to see if it was previously installed using one of them.
url = '%s/repository/previous_changeset_revisions?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_url, url_for( '/', qualified=True ), repository_name, repository_owner, changeset_revision )
url = url_join( tool_shed_url,
'repository/previous_changeset_revisions?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( url_for( '/', qualified=True ), repository_name, repository_owner, changeset_revision ) )
response = urllib2.urlopen( url )
text = response.read()
response.close()
@@ -1350,8 +1356,9 @@ class AdminToolshed( AdminGalaxy ):
# Get the tool_versions from the tool shed for each tool in the installed change set.
repository = get_repository( trans, kwd[ 'id' ] )
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( tool_shed_url, repository.name, repository.owner, repository.changeset_revision )
url = url_join( tool_shed_url,
'repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
( repository.name, repository.owner, repository.changeset_revision ) )
response = urllib2.urlopen( url )
text = response.read()
response.close()
@@ -1522,7 +1529,7 @@ class AdminToolshed( AdminGalaxy ):
def __generate_clone_url( self, trans, repository ):
"""Generate the URL for cloning a repository."""
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
return '%s/repos/%s/%s' % ( tool_shed_url, repository.owner, repository.name )
return url_join( tool_shed_url, 'repos', repository.owner, repository.name )
## ---- Utility methods -------------------------------------------------------
+7 -4
View File
@@ -148,7 +148,8 @@ class DataAdmin( BaseUIController ):
dbkey = build[0]
longname = build[1]
break
assert dbkey is not '?', 'That build was not found'
if dbkey == '?':
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='An invalid build was specified.' )
ftp = ftplib.FTP('hgdownload.cse.ucsc.edu')
ftp.login('anonymous', trans.get_user().email)
checker = []
@@ -189,7 +190,8 @@ class DataAdmin( BaseUIController ):
dbkeys=trans.ucsc_builds )
elif source == 'Ensembl':
dbkey = params.get( 'ensembl_dbkey', None )
assert dbkey is not '?', 'That build was not found'
if dbkey == '?':
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='An invalid build was specified.' )
for build in trans.ensembl_builds:
if build[ 'dbkey' ] == dbkey:
dbkey = build[ 'dbkey' ]
@@ -199,7 +201,7 @@ class DataAdmin( BaseUIController ):
break
url = 'ftp://ftp.ensembl.org/pub/release-%s/fasta/%s/dna/%s.%s.%s.dna.toplevel.fa.gz' % ( release, pathname.lower(), pathname, dbkey, release )
else:
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='Somehow an invalid data source was specified.' )
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='An invalid data source was specified.' )
if url is None:
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='Unable to generate a valid URL with the specified parameters.' )
params = dict( protocol='http', name=dbkey, datatype='fasta', url=url, user=trans.user.id )
@@ -248,7 +250,8 @@ class DataAdmin( BaseUIController ):
sa = trans.app.model.context.current
if jobtype == 'liftover':
job = sa.query( model.TransferJob ).filter_by( id=jobid ).first()
joblabel = 'Download liftOver'
liftover = trans.app.job_manager.deferred_job_queue.plugins['LiftOverTransferPlugin'].get_job_status( jobid )
joblabel = 'Download liftOver (%s to %s)' % ( liftover.params[ 'from_genome' ], liftover.params[ 'to_genome' ] )
elif jobtype == 'transfer':
job = sa.query( model.TransferJob ).filter_by( id=jobid ).first()
joblabel = 'Download Genome'
+9 -6
View File
@@ -203,12 +203,12 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use
job_id=job.id,
job_tool_id=job.tool_id,
job_command_line=job.command_line,
job_stderr=job.stderr,
job_stdout=job.stdout,
job_info=job.info,
job_traceback=job.traceback,
job_stderr=util.unicodify( job.stderr ),
job_stdout=util.unicodify( job.stdout ),
job_info=util.unicodify( job.info ),
job_traceback=util.unicodify( job.traceback ),
email=email,
message=message )
message=util.unicodify( message ) )
frm = to_address
# Check email a bit
email = email.strip()
@@ -644,7 +644,10 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use
dataset = self.get_dataset( trans, id, False, True )
if not dataset:
web.httpexceptions.HTTPNotFound()
return self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
annotation = self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
if annotation and isinstance( annotation, unicode ):
annotation = annotation.encode( 'ascii', 'replace' ) #paste needs ascii here
return annotation
@web.expose
def display_at( self, trans, dataset_id, filename=None, **kwd ):
+97
View File
@@ -0,0 +1,97 @@
import pkg_resources
pkg_resources.require( "bx-python" )
from galaxy.util.json import to_json_string, from_json_string
from galaxy.web.base.controller import *
from galaxy.visualization.phyloviz.phyloviz_dataprovider import Phyloviz_DataProvider
class PhyloVizController( BaseUIController, UsesVisualizationMixin, UsesHistoryDatasetAssociationMixin, SharableMixin ):
"""
Controller for phyloViz browser interface.
"""
def __init__(self, app ):
BaseUIController.__init__( self, app )
@web.expose
@web.require_login()
def index( self, trans, dataset_id = None, **kwargs ):
"""
The index method is called using phyloviz/ with a dataset id passed in.
The relevant data set is then retrieved via get_json_from_datasetId which interfaces with the parser
The json representation of the phylogenetic tree along with the config is then written in the .mako template and passed back to the user
"""
json, config = self.get_json_from_datasetId(trans, dataset_id)
config["saved_visualization"] = False
return trans.fill_template( "visualization/phyloviz.mako", data = json, config=config)
@web.expose
def visualization(self, trans, id):
"""
Called using a viz_id (id) to retrieved stored visualization data (in json format) and all the viz_config
"""
viz = self.get_visualization(trans, id)
config = self.get_visualization_config(trans, viz)
config["saved_visualization"] = True
data = config["root"]
return trans.fill_template( "visualization/phyloviz.mako", data = data, config=config)
@web.expose
@web.json
def load_visualization_json(self, trans, viz_id):
"""
Though not used in current implementation, this provides user with a convenient method to retrieve the viz_data & viz_config via json.
"""
viz = self.get_visualization(trans, viz_id)
viz_config = self.get_visualization_config(trans, viz)
viz_config["saved_visualization"] = True
return {
"data" : viz_config["root"],
"config" : viz_config
}
@web.expose
@web.json
def getJsonData(self, trans, dataset_id, treeIndex=0):
"""
Method to retrieve data asynchronously via json format. Retriving from here rather than
making a direct datasets/ call allows for some processing and event capturing
"""
treeIndex = int(treeIndex)
json, config = self.get_json_from_datasetId(trans, dataset_id, treeIndex)
packedJson = {
"data" : json,
"config" : config
}
return packedJson
def get_json_from_datasetId(self, trans, dataset_id, treeIndex=0):
"""
For interfacing phyloviz controllers with phyloviz visualization data provider (parsers)
"""
dataset = self.get_dataset(trans, dataset_id)
fileExt, filepath = dataset.ext, dataset.file_name # .name stores the name of the dataset from the orginal upload
json, config = "", {} # config contains properties of the tree and file
if fileExt == "json":
something, json = self.get_data(dataset)
else:
try:
pd = Phyloviz_DataProvider()
json, config = pd.parseFile(filepath, fileExt)
json = json[treeIndex]
except Exception:
pass
config["title"] = dataset.display_name()
config["ext"] = fileExt
config["dataset_id"] = dataset_id
config["treeIndex"] = treeIndex
return json, config
@@ -16,6 +16,10 @@ class VisualizationListGrid( grids.Grid ):
action = "paramamonster"
elif item.type == "circster":
action = "circster"
elif item.type == "phyloviz":
# Support phyloviz
controller = "phyloviz"
action = "visualization"
return dict( controller=controller, action=action, id=item.id )
# Grid definition
@@ -11,7 +11,7 @@ from galaxy.util.json import from_json_string, to_json_string
from galaxy.model.orm import *
from galaxy.util.shed_util import create_repo_info_dict, get_changectx_for_changeset, get_configured_ui, get_repository_file_contents, NOT_TOOL_CONFIGS
from galaxy.util.shed_util import open_repository_files_folder, reversed_lower_upper_bounded_changelog, reversed_upper_bounded_changelog, strip_path
from galaxy.util.shed_util import to_html_escaped, update_repository
from galaxy.util.shed_util import to_html_escaped, update_repository, url_join
from galaxy.tool_shed.encoding_util import *
from common import *
@@ -246,6 +246,25 @@ class EmailAlertsRepositoryListGrid( RepositoryListGrid ):
grids.GridAction( "User preferences", dict( controller='user', action='index', cntrller='repository', webapp='community' ) )
]
class WritableRepositoryListGrid( RepositoryListGrid ):
def build_initial_query( self, trans, **kwd ):
# TODO: improve performance by adding a db table associating users with repositories for which they have write access.
username = kwd[ 'username' ]
clause_list = []
for repository in trans.sa_session.query( self.model_class ):
allow_push_usernames = repository.allow_push.split( ',' )
if username in allow_push_usernames:
clause_list.append( self.model_class.table.c.id == repository.id )
if clause_list:
return trans.sa_session.query( self.model_class ) \
.filter( or_( *clause_list ) ) \
.join( model.User.table ) \
.outerjoin( model.RepositoryCategoryAssociation.table ) \
.outerjoin( model.Category.table )
# Return an empty query.
return trans.sa_session.query( self.model_class ) \
.filter( self.model_class.table.c.id < 0 )
class ValidRepositoryListGrid( RepositoryListGrid ):
class CategoryColumn( grids.TextColumn ):
def get_value( self, trans, grid, repository ):
@@ -393,6 +412,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
email_alerts_repository_list_grid = EmailAlertsRepositoryListGrid()
category_list_grid = CategoryListGrid()
valid_category_list_grid = ValidCategoryListGrid()
writable_repository_list_grid = WritableRepositoryListGrid()
def __add_hgweb_config_entry( self, trans, repository, repository_path ):
# Add an entry in the hgweb.config file for a new repository. An entry looks something like:
@@ -519,12 +539,15 @@ class RepositoryController( BaseUIController, ItemRatings ):
repository_id = kwd.get( 'id', None )
repository = get_repository( trans, repository_id )
kwd[ 'f-email' ] = repository.user.email
elif operation == "my_repositories":
elif operation == "repositories_i_own":
# Eliminate the current filters if any exist.
for k, v in kwd.items():
if k.startswith( 'f-' ):
del kwd[ k ]
kwd[ 'f-email' ] = trans.user.email
elif operation == "writable_repositories":
kwd[ 'username' ] = trans.user.username
return self.writable_repository_list_grid( trans, **kwd )
elif operation == "repositories_by_category":
# Eliminate the current filters if any exist.
for k, v in kwd.items():
@@ -726,9 +749,10 @@ class RepositoryController( BaseUIController, ItemRatings ):
update = 'true'
no_update = 'false'
else:
# Start building up the url to redirect back to the calling Galaxy instance.
url = '%sadmin_toolshed/update_to_changeset_revision?tool_shed_url=%s' % ( galaxy_url, url_for( '/', qualified=True ) )
url += '&name=%s&owner=%s&changeset_revision=%s&latest_changeset_revision=' % ( repository.name, repository.user.username, changeset_revision )
# Start building up the url to redirect back to the calling Galaxy instance.
url = url_join( galaxy_url,
'admin_toolshed/update_to_changeset_revision?tool_shed_url=%s&name=%s&owner=%s&changeset_revision=%s&latest_changeset_revision=' % \
( url_for( '/', qualified=True ), repository.name, repository.user.username, changeset_revision ) )
if changeset_revision == repository.tip:
# If changeset_revision is the repository tip, there are no additional updates.
if from_update_manager:
@@ -1372,10 +1396,9 @@ class RepositoryController( BaseUIController, ItemRatings ):
"""Send the list of repository_ids and changeset_revisions to Galaxy so it can begin the installation process."""
galaxy_url = trans.get_cookie( name='toolshedgalaxyurl' )
# Redirect back to local Galaxy to perform install.
url = '%sadmin_toolshed/prepare_for_install' % galaxy_url
url += '?tool_shed_url=%s' % url_for( '/', qualified=True )
url += '&repository_ids=%s' % ','.join( util.listify( repository_ids ) )
url += '&changeset_revisions=%s' % ','.join( util.listify( changeset_revisions ) )
url = url_join( galaxy_url,
'admin_toolshed/prepare_for_install?tool_shed_url=%s&repository_ids=%s&changeset_revisions=%s' % \
( url_for( '/', qualified=True ), ','.join( util.listify( repository_ids ) ), ','.join( util.listify( changeset_revisions ) ) ) )
return trans.response.send_redirect( url )
@web.expose
def load_invalid_tool( self, trans, repository_id, tool_config, changeset_revision, **kwd ):
+955
View File
@@ -0,0 +1,955 @@
var UserMenuBase = Backbone.View.extend({
/**
* Base class of any menus that takes in user interaction. Contains checking methods.
*/
className: 'UserMenuBase',
isAcceptableValue : function ($inputKey, min, max) {
/**
* Check if an input value is a number and falls within max min.
*/
var self = this,
value = $inputKey.val(),
fieldName = $inputKey.attr("displayLabel") || $inputKey.attr("id").replace("phyloViz", "");
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
if (!isNumeric(value)){
alert(fieldName + " is not a number!");
return false;
}
if ( value > max){
alert(fieldName + " is too large.");
return false;
} else if ( value < min) {
alert(fieldName + " is too small.");
return false;
}
return true;
},
hasIllegalJsonCharacters : function($inputKey) {
/**
* Check if any user string inputs has illegal characters that json cannot accept
*/
if ($inputKey.val().search(/"|'|\\/) !== -1){
alert("Named fields cannot contain these illegal characters: double quote(\"), single guote(\'), or back slash(\\). ");
return true;
}
return false;
}
});
function PhyloTreeLayout() {
/**
* -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree.
* -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent
* 2) Nodes must appear in other after they have expand and contracted
*/
var self = this,
hierarchy = d3.layout.hierarchy().sort(null).value(null),
height = 360, // ! represents both the layout angle and the height of the layout, in px
layoutMode = "Linear",
leafHeight = 18, // height of each individual leaf node
depthSeparation = 200, // separation between nodes of different depth, in px
leafIndex = 0, // change to recurssive call
defaultDist = 0.5, // tree defaults to 0.5 dist if no dist is specified
maxTextWidth = 50; // maximum length of the text labels
self.leafHeight = function(inputLeafHeight){
if (typeof inputLeafHeight === "undefined"){ return leafHeight; }
else { leafHeight = inputLeafHeight; return self;}
};
self.layoutMode = function(mode){
if (typeof mode === "undefined"){ return layoutMode; }
else { layoutMode = mode; return self;}
};
self.layoutAngle = function(angle) { // changes the layout angle of the display, which is really changing the height
if (typeof angle === "undefined"){ return height; }
if (isNaN(angle) || angle < 0 || angle > 360) { return self; } // to use default if the user puts in strange values
else { height = angle; return self;}
};
self.separation = function(dist){ // changes the dist between the nodes of different depth
if (typeof dist === "undefined"){ return depthSeparation; }
else { depthSeparation = dist; return self;}
};
self.links = function (nodes) { // uses d3 native method to generate links. Done.
return d3.layout.tree().links(nodes);
};
// -- Custom method for laying out phylogeny tree in a linear fashion
self.nodes = function (d, i) {
var _nodes = hierarchy.call(self, d, i), // self is to find the depth of all the nodes, assumes root is passed in
nodes = [],
maxDepth = 0,
numLeaves = 0;
// changing from hierarchy's custom format for data to usable format
_nodes.forEach(function (_node){
var node = _node.data;
node.depth = _node.depth;
maxDepth = node.depth > maxDepth ? node.depth : maxDepth; //finding max depth of tree
nodes.push(node);
});
// counting the number of leaf nodes and assigning max depth to nodes that do not have children to flush all the leave nodes
nodes.forEach(function(node){
if ( !node.children ) { //&& !node._children
numLeaves += 1;
node.depth = maxDepth; // if a leaf has no child it would be assigned max depth
}
});
leafHeight = layoutMode === "Circular" ? height / numLeaves : leafHeight;
leafIndex = 0;
layout(nodes[0], maxDepth, leafHeight, null);
return nodes;
};
function layout (node, maxDepth, vertSeparation, parent) {
/**
* -- Function with side effect of adding x0, y0 to all child; take in the root as starting point
* assuming that the leave nodes would be sorted in presented order
* horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent
* vertical (x0) - if leave node: find its order in all of the leave node === node.id, then multiply by verticalSeparation
* - if parent node: is place in the mid point all of its children nodes
* -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning
*/
var children = node.children,
sumChildVertSeparation = 0;
// calculation of node's dist from parents, going down.
var dist = node.dist || defaultDist;
dist = dist > 1 ? 1 : dist; // We constrain all dist to be less than one
node.dist = dist;
if (parent !== null){
node.y0 = parent.y0 + dist * depthSeparation;
} else { //root node
node.y0 = maxTextWidth;
}
// if a node have no children, we will treat it as a leaf and start laying it out first
if (!children) {
node.x0 = leafIndex++ * vertSeparation;
} else {
// if it has children, we will visit all its children and calculate its position from its children
children.forEach( function (child) {
child.parent = node;
sumChildVertSeparation += layout(child, maxDepth, vertSeparation, node);
});
node.x0 = sumChildVertSeparation / children.length;
}
// adding properties to the newly created node
node.x = node.x0;
node.y = node.y0;
return node.x0;
}
return self;
}
/**
* -- PhyloTree Model --
*/
var PhyloTree = Visualization.extend({
defaults : {
layout: "Linear",
separation : 250, // px dist between nodes of different depth to represent 1 evolutionary until
leafHeight: 18,
type : "phyloviz", // visualization type
title : "Title",
scaleFactor: 1,
translate: [0,0],
fontSize: 12, //fontSize of node label
selectedNode : null,
nodeAttrChangedTime : 0
},
root : {}, // Root has to be its own independent object because it is not part of the viz_config
toggle : function (d) {
/**
* Mechanism to expand or contract a single node. Expanded nodes have a children list, while for
* contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not have their
* children rendered.
*/
if(typeof d === "undefined") {return ;}
if (d.children ) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
},
toggleAll : function(d) {
/**
* Contracts the phylotree to a single node by repeatedly calling itself to place all the list
* of children under _children.
*/
if (d.children && d.children.length !== 0) {
d.children.forEach(this.toggleAll);
toggle(d);
}
},
getData : function (){
/**
* Return the data of the tree. Used for preserving state.
*/
return this.root;
},
save: function() {
/**
* Overriding the default save mechanism to do some clean of circular reference of the
* phyloTree and to include phyloTree in the saved json
*/
var root = this.root;
cleanTree(root);
this.set("root", root);
function cleanTree(node){
// we need to remove parent to delete circular reference
delete node.parent;
// removing unnecessary attributes
if (node._selected){ delete node._selected;}
node.children ? node.children.forEach(cleanTree) : 0;
node._children ? node._children.forEach(cleanTree) : 0;
}
var config = jQuery.extend(true, {}, this.attributes);
config["selectedNode"] = null;
show_message("Saving to Galaxy", "progress");
return $.ajax({
url: this.url(),
type: "POST",
dataType: "json",
data: {
vis_json: JSON.stringify(config)
},
success: function(res){
var viz_id = res.url.split("id=")[1].split("&")[0],
viz_url = "/phyloviz/visualization?id=" + viz_id;
window.history.pushState({}, "", viz_url + window.location.hash);
hide_modal();
}
});
}
});
/**
* -- Views --
*/
var PhylovizLayoutBase = Backbone.View.extend({
/**
* Stores the default variable for setting up the visualization
*/
defaults : {
nodeRadius : 4.5 // radius of each node in the diagram
},
stdInit : function (options) {
/**
* Common initialization in layouts
*/
var self = this;
self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", self.updateAndRender, self);
self.vis = options.vis;
self.i = 0;
self.maxDepth = -1; // stores the max depth of the tree
self.width = options.width;
self.height = options.height;
},
updateAndRender : function(source) {
/**
* Updates the visualization whenever there are changes in the expansion and contraction of nodes
* AND possibly when the tree is edited.
*/
var vis = d3.select(".vis"),
self = this;
source = source || self.model.root;
self.renderNodes(source);
self.renderLinks(source);
self.addTooltips();
},
renderLinks : function(source) {
/**
* Renders the links for the visualization.
*/
var self = this;
var diagonal = self.diagonal;
var duration = self.duration;
var layoutMode = self.layoutMode;
var link = self.vis.selectAll("g.completeLink")
.data(self.tree.links(self.nodes), function(d) { return d.target.id; });
var calcalateLinePos = function(d) {
d.pos0 = d.source.y0 + " " + d.source.x0; // position of the source node <=> starting location of the line drawn
d.pos1 = d.source.y0 + " " + d.target.x0; // position where the line makes a right angle bend
d.pos2 = d.target.y0 + " " + d.target.x0; // point where the horizontal line becomes a dotted line
};
var linkEnter = link.enter().insert("svg:g","g.node")
.attr("class", "completeLink");
linkEnter.append("svg:path")
.attr("class", "link")
.attr("d", function(d) {
calcalateLinePos(d);
return "M " + d.pos0 + " L " + d.pos1;
});
var linkUpdate = link.transition().duration(500);
linkUpdate.select("path.link")
.attr("d", function(d) {
calcalateLinePos(d);
return "M " + d.pos0 + " L " + d.pos1 + " L " + d.pos2;
});
var linkExit = link.exit().remove();
},
// User Interaction methods below
selectNode : function(node){
/**
* Displays the information for editting
*/
var self = this;
d3.selectAll("g.node")
.classed("selectedHighlight", function(d){
if (node.id === d.id){
if(node._selected) { // for de=selecting node.
delete node._selected;
return false;
} else {
node._selected = true;
return true;
}
}
return false;
});
self.model.set("selectedNode", node);
$("#phyloVizSelectedNodeName").val(node.name);
$("#phyloVizSelectedNodeDist").val(node.dist);
$("#phyloVizSelectedNodeAnnotation").val(node.annotation || "");
},
addTooltips : function (){
/**
* Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated
* enterNodes
*/
$(".bs-tooltip").remove(); //clean up tooltip, just in case its listeners are removed by d3
$(".node")
.attr("data-original-title", function(){
var d = this.__data__,
annotation = d.annotation || "None" ;
return d ? (d.name ? d.name + "<br/>" : "") + "Dist: " + d.dist + " <br/>Annotation: " + annotation: "";
})
.tooltip({'placement':'top', 'trigger' : 'hover'});
}
});
var PhylovizLinearView = PhylovizLayoutBase.extend({
/**
* Linea layout class of Phyloviz, is responsible for rendering the nodes
* calls PhyloTreeLayout to determine the positions of the nodes
*/
initialize : function(options){
// Default values of linear layout
var self = this;
self.margins = options.margins;
self.layoutMode = "Linear";
self.stdInit(options);
self.layout();
self.updateAndRender(self.model.root);
},
layout : function() {
/**
* Creates the basic layout of a linear tree by precalculating fixed values.
* One of calculations are also made here
*/
var self = this;
self.tree = new PhyloTreeLayout().layoutMode("Linear");
self.diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x ]; });
},
renderNodes : function (source) {
/**
* Renders the nodes base on Linear layout.
*/
var self = this,
fontSize = self.model.get("fontSize") + "px";
// assigning properties from models
self.tree.separation(self.model.get("separation")).leafHeight(self.model.get("leafHeight"));
var duration = 500,
nodes = self.tree.separation(self.model.get("separation")).nodes(self.model.root);
var node = self.vis.selectAll("g.node")
.data(nodes, function(d) { return d.name + d.id || (d.id = ++self.i); });
// These variables has to be passed into update links which are in the base methods
self.nodes = nodes;
self.duration = duration;
// ------- D3 ENTRY --------
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", function(){ d3.event.stopPropagation(); })
.on("click", function(d) {
if (d3.event.altKey) {
self.selectNode(d); // display info if alt is pressed
} else {
if(d.children && d.children.length === 0){ return;} // there is no need to toggle leaves
self.model.toggle(d); // contract/expand nodes at data level
self.updateAndRender(d); // re-render the tree
}
});
nodeEnter.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; });
nodeEnter.append("svg:circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("svg:text")
.attr("class", "nodeLabel")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.style("fill-opacity", 1e-6);
// ------- D3 TRANSITION --------
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration);
nodeUpdate.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", self.defaults.nodeRadius)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1)
.style("font-size", fontSize)
.text(function(d) { return d.name; });
// ------- D3 EXIT --------
// Transition exiting nodes to the parent's new position.
var nodeExit =node.exit().transition()
.duration(duration)
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x; // we need the x0, y0 for parents with children
d.y0 = d.y;
});
}
});
var PhylovizView = Backbone.View.extend({
className: 'phyloviz',
initialize: function(options) {
var self = this;
// -- Default values of the vis
self.MIN_SCALE = 0.05; //for zooming
self.MAX_SCALE = 5;
self.MAX_DISPLACEMENT = 500;
self.margins = [10, 60, 10, 80];
self.width = $("#PhyloViz").width();
self.height = $("#PhyloViz").height();
self.radius = self.width;
self.data = options.data;
// -- Events Phyloviz view responses to
$(window).resize(function(){
self.width = $("#PhyloViz").width();
self.height = $("#PhyloViz").height();
self.render();
});
// -- Create phyloTree model
self.phyloTree = new PhyloTree(options.config);
self.phyloTree.root = self.data;
// -- Set up UI functions of main view
self.zoomFunc = d3.behavior.zoom().scaleExtent([self.MIN_SCALE, self.MAX_SCALE]);
self.zoomFunc.translate(self.phyloTree.get("translate"));
self.zoomFunc.scale(self.phyloTree.get("scaleFactor"));
// -- set up header buttons, search and settings menu
self.navMenu = new HeaderButtons(self);
self.settingsMenu = new SettingsMenu({phyloTree : self.phyloTree});
self.nodeSelectionView = new NodeSelectionView({phyloTree : self.phyloTree});
self.search = new PhyloVizSearch();
setTimeout(function(){ // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config
self.zoomAndPan();
}, 1000);
},
render: function(){
// -- Creating helper function for vis. --
var self = this;
$("#PhyloViz").empty();
// -- Layout viz. --
self.mainSVG = d3.select("#PhyloViz").append("svg:svg")
.attr("width", self.width)
.attr("height", self.height)
.attr("pointer-events", "all")
.call(self.zoomFunc.on("zoom", function(){
self.zoomAndPan();
}));
self.boundingRect = self.mainSVG.append("svg:rect")
.attr("class", "boundingRect")
.attr("width", self.width)
.attr("height", self.height)
.attr("stroke", "black")
.attr("fill", "white");
self.vis = self.mainSVG
.append("svg:g")
.attr("class", "vis");
self.layoutOptions = {
model : self.phyloTree,
width : self.width,
height : self.height,
vis: self.vis,
margins: self.margins
};
// -- Creating Title
$("#title").text("Phylogenetic Tree from " + self.phyloTree.get("title") + ":");
// -- Create Linear view instance --
var linearView = new PhylovizLinearView(self.layoutOptions)
},
zoomAndPan : function(event){
/**
* Function to zoom and pan the svg element which the entire tree is contained within
* Uses d3.zoom events, and extend them to allow manual updates and keeping states in model
*/
if (typeof event !== "undefined") {
var zoomParams = event.zoom,
translateParams = event.translate;
}
var self = this,
scaleFactor = self.zoomFunc.scale(),
translationCoor = self.zoomFunc.translate(),
zoomStatement = "",
translateStatement = "";
// Do manual scaling.
switch (zoomParams) {
case "reset":
scaleFactor = 1.0;
translationCoor = [0,0]; break;
case "+":
scaleFactor *= 1.1; break;
case "-":
scaleFactor *= 0.9; break;
default:
if (typeof zoomParams === "number") {
scaleFactor = zoomParams;
} else if (d3.event !== null) {
scaleFactor = d3.event.scale;
}
}
if (scaleFactor < self.MIN_SCALE || scaleFactor > self.MAX_SCALE) { return;}
self.zoomFunc.scale(scaleFactor); //update scale Factor
zoomStatement = "translate(" + self.margins[3] + "," + self.margins[0] + ")" +
" scale(" + scaleFactor + ")";
// Do manual translation.
if( d3.event !== null) {
translateStatement = "translate(" + d3.event.translate + ")";
} else {
if(typeof translateParams !== "undefined") {
var x = translateParams.split(",")[0];
var y = translateParams.split(",")[1];
if (!isNaN(x) && !isNaN(y)){
translationCoor = [translationCoor[0] + parseFloat(x), translationCoor[1] + parseFloat(y)];
}
}
self.zoomFunc.translate(translationCoor); // update zoomFunc
translateStatement = "translate(" + translationCoor + ")";
}
self.phyloTree.set("scaleFactor", scaleFactor);
self.phyloTree.set("translate", translationCoor);
self.vis.attr("transform", translateStatement + zoomStatement); //refers to the view that we are actually zooming
},
reloadViz : function() {
/**
* Primes the Ajax URL to load another Nexus tree
*/
var self = this,
treeIndex = $("#phylovizNexSelector :selected").val(),
dataset_id = self.phyloTree.get("dataset_id"),
url = "phyloviz/getJsonData?dataset_id=" + dataset_id + "&treeIndex=" + String(treeIndex);
$.getJSON(url, function(packedJson){
window.initPhyloViz(packedJson.data, packedJson.config);
});
}
});
var HeaderButtons = Backbone.View.extend({
initialize : function(phylovizView){
var self = this;
self.phylovizView = phylovizView;
// Clean up code - if the class initialized more than once
$("#panelHeaderRightBtns").empty();
$("#phyloVizNavBtns").empty();
$("#phylovizNexSelector").off();
self.initNavBtns();
self.initRightHeaderBtns();
// Initial a tree selector in the case of nexus
$("#phylovizNexSelector").off().on("change", function() {self.phylovizView.reloadViz();} );
},
initRightHeaderBtns : function(){
var self = this;
rightMenu = create_icon_buttons_menu([
{ icon_class: 'gear', title: 'PhyloViz Settings', on_click: function(){
$("#SettingsMenu").show();
self.settingsMenu.updateUI();
} },
{ icon_class: 'disk', title: 'Save visualization', on_click: function() {
var nexSelected = $("#phylovizNexSelector option:selected").text();
if(nexSelected) {
self.phylovizView.phyloTree.set("title", nexSelected);
}
self.phylovizView.phyloTree.save();
} },
{ icon_class: 'chevron-expand', title: 'Search / Edit Nodes', on_click: function() {
$("#nodeSelectionView").show();
} },
{ icon_class: 'information', title: 'Phyloviz Help', on_click: function() {
window.open('http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree');
// https://docs.google.com/document/d/1AXFoJgEpxr21H3LICRs3EyMe1B1X_KFPouzIgrCz3zk/edit
} }
],
{
tooltip_config: { placement: 'bottom' }
});
$("#panelHeaderRightBtns").append(rightMenu.$el);
},
initNavBtns: function() {
var self = this,
navMenu = create_icon_buttons_menu([
{ icon_class: 'zoom-in', title: 'Zoom in', on_click: function() {
self.phylovizView.zoomAndPan({ zoom : "+"});
} },
{ icon_class: 'zoom-out', title: 'Zoom out', on_click: function() {
self.phylovizView.zoomAndPan({ zoom : "-"});
} },
{ icon_class: 'arrow-circle', title: 'Reset Zoom/Pan', on_click: function() {
self.phylovizView.zoomAndPan({ zoom : "reset"});
} }
],
{
tooltip_config: { placement: 'bottom' }
});
$("#phyloVizNavBtns").append(navMenu.$el);
}
});
var SettingsMenu = UserMenuBase.extend({
className: 'Settings',
initialize: function(options){
// settings needs to directly interact with the phyloviz model so it will get access to it.
var self = this;
self.phyloTree = options.phyloTree;
self.el = $("#SettingsMenu");
self.inputs = {
separation : $("#phyloVizTreeSeparation"),
leafHeight : $("#phyloVizTreeLeafHeight"),
fontSize : $("#phyloVizTreeFontSize")
};
//init all buttons of settings
$("#settingsCloseBtn").off().on("click", function() { self.el.hide(); });
$("#phylovizResetSettingsBtn").off().on("click", function() { self.resetToDefaults(); });
$("#phylovizApplySettingsBtn").off().on("click", function() { self.apply(); });
},
apply : function(){
/**
* Applying user values to phylotree model.
*/
var self = this;
if (!self.isAcceptableValue(self.inputs["separation"], 50, 2500) ||
!self.isAcceptableValue(self.inputs["leafHeight"], 5, 30) ||
!self.isAcceptableValue(self.inputs["fontSize"], 5, 20)){
return;
}
$.each(self.inputs, function(key, $input){
self.phyloTree.set(key, $input.val());
});
},
updateUI : function(){
/**
* Called to update the values input to that stored in the model
*/
var self = this;
$.each(self.inputs, function(key, $input){
$input.val(self.phyloTree.get(key));
});
},
resetToDefaults : function(){
/**
* Resets the value of the phyloTree model to its default
*/
$(".bs-tooltip").remove(); // just in case the tool tip was not removed
var self = this;
$.each(self.phyloTree.defaults, function(key, value) {
self.phyloTree.set(key, value);
});
self.updateUI();
},
render: function(){
}
});
var NodeSelectionView = UserMenuBase.extend({
/**
* View for inspecting node properties and editing them
*/
className: 'Settings',
initialize : function (options){
var self = this;
self.el = $("#nodeSelectionView");
self.phyloTree = options.phyloTree;
self.UI = {
enableEdit : $('#phylovizEditNodesCheck'),
saveChanges : $('#phylovizNodeSaveChanges'),
cancelChanges : $("#phylovizNodeCancelChanges"),
name : $("#phyloVizSelectedNodeName"),
dist : $("#phyloVizSelectedNodeDist"),
annotation : $("#phyloVizSelectedNodeAnnotation")
};
self.valuesOfConcern = {
name : null,
dist : null,
annotation : null
}; // temporarily stores the values in case user change their mind
//init UI buttons
$("#nodeSelCloseBtn").off().on("click", function() { self.el.hide(); });
self.UI.saveChanges.off().on("click", function(){ self.updateNodes(); });
self.UI.cancelChanges.off().on("click", function(){ self.cancelChanges(); });
(function ($) {
// extending jquery fxn for enabling and disabling nodes.
$.fn.enable = function (isEnabled) {
return $(this).each(function () {
if(isEnabled){
$(this).removeAttr('disabled');
} else {
$(this).attr('disabled', 'disabled');
}
});
};
})(jQuery);
self.UI.enableEdit.off().on("click", function () {
self.toggleUI();
});
},
toggleUI : function(){
/**
* For turning on and off the child elements
*/
var self = this,
checked = self.UI.enableEdit.is(':checked');
!checked ? self.cancelChanges() : "";
$.each(self.valuesOfConcern, function(key, value) {
self.UI[key].enable(checked);
});
if(checked){
self.UI.saveChanges.show();
self.UI.cancelChanges.show();
} else {
self.UI.saveChanges.hide();
self.UI.cancelChanges.hide();
}
},
cancelChanges : function() {
/**
* Reverting to previous values in case user change their minds
*/
var self = this,
node = self.phyloTree.get("selectedNode");
if (node){
$.each(self.valuesOfConcern, function(key, value) {
self.UI[key].val(node[key]);
});
}
},
updateNodes : function (){
/**
* Changing the data in the underlying tree with user-specified values
*/
var self = this,
node = self.phyloTree.get("selectedNode");
if (node){
if (!self.isAcceptableValue(self.UI.dist, 0, 1) ||
self.hasIllegalJsonCharacters(self.UI.name) ||
self.hasIllegalJsonCharacters(self.UI.annotation) ) {
return;
}
$.each(self.valuesOfConcern, function(key, value) {
(node[key]) = self.UI[key].val();
});
self.phyloTree.set("nodeAttrChangedTime", new Date());
} else {
alert("No node selected");
}
}
});
var PhyloVizSearch = UserMenuBase.extend({
/**
* Initializes the search panel on phyloviz and handles its user interaction
* It allows user to search the entire free based on some qualifer, like dist <= val.
*/
initialize : function () {
var self = this;
$("#phyloVizSearchBtn").on("click", function(){
var searchTerm = $("#phyloVizSearchTerm"),
searchConditionVal = $("#phyloVizSearchCondition").val().split("-"),
attr = searchConditionVal[0],
condition = searchConditionVal[1];
self.hasIllegalJsonCharacters(searchTerm);
if (attr === "dist"){
self.isAcceptableValue(searchTerm, 0, 1);
}
self.searchTree(attr, condition, searchTerm.val());
});
},
searchTree : function (attr, condition, val){
/**
* Searches the entire tree and will highlight the nodes that match the condition in green
*/
d3.selectAll("g.node")
.classed("searchHighlight", function(d){
var attrVal = d[attr];
if (typeof attrVal !== "undefined" && attrVal !== null){
if (attr === "dist"){
switch (condition) {
case "greaterEqual":
return attrVal >= +val;
case "lesserEqual":
return attrVal <= +val;
default:
return;
}
} else if (attr === "name" || attr === "annotation") {
return attrVal.toLowerCase().indexOf(val.toLowerCase()) !== -1;
}
}
});
}
});
+1 -1
View File
@@ -58,7 +58,7 @@
Info:
</label>
<div style="float: left; width: 250px; margin-right: 10px;">
<textarea name="info" cols="40" rows="2">${data.info | h}</textarea>
<textarea name="info" cols="40" rows="2">${ util.unicodify( data.info ) | h}</textarea>
</div>
<div style="clear: both"></div>
</div>
+4 -4
View File
@@ -24,21 +24,21 @@
<% job = hda.creating_job_associations[0].job %>
%if job.traceback:
The Galaxy framework encountered the following error while attempting to run the tool:
<pre>${job.traceback | h}</pre>
<pre>${ util.unicodify( job.traceback ) | h}</pre>
%endif
%if job.stderr or job.info:
Tool execution generated the following error message:
%if job.stderr:
<pre>${job.stderr | h}</pre>
<pre>${ util.unicodify( job.stderr ) | h}</pre>
%elif job.info:
<pre>${job.info | h}</pre>
<pre>${ util.unicodify( job.info ) | h}</pre>
%endif
%else:
Tool execution did not generate any error messages.
%endif
%if job.stdout:
The tool produced the following additional output:
<pre>${job.stdout | h}</pre>
<pre>${ util.unicodify( job.stdout ) | h}</pre>
%endif
%else:
The tool did not create any additional job / error info.
+11
View File
@@ -272,6 +272,17 @@ $(function() {
}
init_trackster_links();
function init_phyloviz_links() {
// PhyloViz links
// Add to trackster browser functionality
$(".phyloviz-add").live("click", function() {
var dataset = this,
dataset_jquery = $(this);
window.parent.location = dataset_jquery.attr("new-url");
});
}
init_phyloviz_links();
// History rename functionality.
async_save_text("history-name-container", "history-name", "${h.url_for( controller="/history", action="rename_async", id=trans.security.encode_id(history.id) )}", "new_name", 18);
+11
View File
@@ -29,6 +29,9 @@
## Render the dataset `data` as history item, using `hid` as the displayed id
<%def name="render_dataset( data, hid, show_deleted_on_refresh = False, for_editing = True, display_structured = False )">
<%
from galaxy.datatypes.xml import Phyloxml
from galaxy.datatypes.data import Newick, Nexus
dataset_id = trans.security.encode_id( data.id )
if data.state in ['no state','',None]:
@@ -230,6 +233,14 @@
action-url="${h.url_for( controller='tracks', action='browser', dataset_id=dataset_id)}"
new-url="${h.url_for( controller='tracks', action='index', dataset_id=dataset_id, default_dbkey=data.dbkey)}" title="View in Trackster"></a>
%endif
<%
isPhylogenyData = isinstance(data.datatype, (Phyloxml, Nexus, Newick))
%>
%if isPhylogenyData:
<a href="javascript:void(0)" class="icon-button chart_curve phyloviz-add"
action-url="${h.url_for( controller='phyloviz', action='-', dataset_id=dataset_id)}"
new-url="${h.url_for( controller='phyloviz', action='index', dataset_id=dataset_id)}" title="View in Phyloviz"></a>
%endif
%if trans.user:
%if not display_structured:
<div style="float: right">
+320
View File
@@ -0,0 +1,320 @@
<%inherit file="/webapps/galaxy/base_panels.mako"/>
##
<%def name="init()">
<%
self.has_left_panel=False
self.has_right_panel=False
self.active_view="visualization"
self.message_box_visible=False
%>
</%def>
<%def name="stylesheets()">
${parent.stylesheets()}
<style>
.node circle {
cursor: pointer;
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node.searchHighlight circle {
stroke-width: 3px;
stroke: #7adc26;
}
.node.selectedHighlight circle {
stroke-width: 3px;
stroke: #dc143c;
}
path.link {
fill: none;
stroke: #B5BBFF;
stroke-width: 4.0px;
}
div #phyloVizNavContainer{
text-align: center;
width: 100%;
height: 0px;
}
div #phyloVizNav{
font-weight: bold;
display: inline-block;
background: transparent;
top: -2em;
position: relative;
}
div .navControl{
float: left;
}
div#FloatingMenu {
left: 0;
top: 15%;
width:20%;
z-index:100;
padding: 5px;
}
div#SettingsMenu {
width: 25%;
top: 350px;
}
div#nodeSelectionView {
width: 25%;
top:70px;
}
.Panel {
right: 0%;
z-index: 101;
position: fixed;
## Borrowed from galaxy modal_dialogues
background-color: white;
border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.3);
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
}
span.PhylovizCloseBtn{
cursor: pointer;
float : right;
}
#PhyloViz{
width: 100%;
height: 95%;
}
h2.PhyloVizMenuTitle{
color: white;
}
## Settings Menu
.SettingMenuRows{
margin: 2px 0 2px 0;
}
## Helper Styles
.PhyloVizFloatLeft{
float : left;
}
.icon-button.zoom-in,.icon-button.zoom-out{display:inline-block;height:16px;width:16px;margin-bottom:-3px;cursor:pointer;}
.icon-button.zoom-out{background:transparent url(../images/fugue/magnifier-zoom-out.png) center center no-repeat;}
.icon-button.zoom-in{margin-left:10px;background:transparent url(../images/fugue/magnifier-zoom.png) center center no-repeat;}
</style>
</%def>
<%def name="javascripts()">
${parent.javascripts()}
${h.js( "galaxy.panels", "libs/d3", "mvc/data", "viz/visualization", "viz/phyloviz")}
</%def>
<%def name="center_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<div style="float:left;" id="title"></div>
<div style="float:right;" id="panelHeaderRightBtns"></div>
</div>
<div style="clear: both"></div>
</div>
<div id="phyloVizNavContainer">
<div id="phyloVizNav">
%if config["ext"] == "nex" and not config["saved_visualization"]:
<div id = "phylovizNexInfo" class="navControl">
<p>Select a tree to view: &nbsp;&nbsp;
<select id="phylovizNexSelector">
% for tree, index in config["trees"]:
<option value="${index}">${tree}</option>
% endfor
</select>
</p>
</div>
%endif
<div id="phyloVizNavBtns" class="navControl">
</div>
<div class="navControl">
<p>&nbsp;| Alt+click to select nodes</p>
</div>
</div>
</div>
## Node Selection Menu
<div id="nodeSelectionView" class="Panel">
<div class="modal-header">
<h3 class="PhyloVizMenuTitle">Search / Edit Nodes :
<span class="PhylovizCloseBtn" id="nodeSelCloseBtn"> X </span>
</h3>
</div>
<div class="modal-body">
<div class="SettingMenuRows">
Search for nodes with:
<select id="phyloVizSearchCondition" style="width: 55%">
<option value="name-containing">Name (containing)</option>
<option value="annotation-containing">Annotation (containing)</option>
<option value="dist-greaterEqual">Distance (>=)</option>
<option value="dist-lesserEqual">Distance (<=)</option>
</select>
<input type="text" id="phyloVizSearchTerm" value="None" size="15" displayLabel="Distance">
<div class="SettingMenuRows" style="text-align: center;">
<button id="phyloVizSearchBtn" > Search! </button>
</div>
</div>
<br/>
<div class="SettingMenuRows">
Name: <input type="text" id="phyloVizSelectedNodeName" value="None" size="15" disabled="disabled" >
</div>
<div class="SettingMenuRows">
Dist: <input type="text" id="phyloVizSelectedNodeDist" value="None" size="15" disabled="disabled" displayLabel="Distance">
</div>
<div class="SettingMenuRows">
Annotation:
<textarea id="phyloVizSelectedNodeAnnotation" disabled="disabled" ></textarea>
</div>
<div class="SettingMenuRows">
Edit: <input type="checkbox" id="phylovizEditNodesCheck" value="You can put custom annotations here and it will be saved">
<button id="phylovizNodeSaveChanges" style="display: none;"> Save edits</button>
<button id="phylovizNodeCancelChanges" style="display: none;"> Cancel</button>
</div>
</div>
</div>
## Settings Menus
<div id="SettingsMenu" class="Panel">
<div class="modal-header">
<h3 class="PhyloVizMenuTitle">Phyloviz Settings:
<span class="PhylovizCloseBtn" id="settingsCloseBtn"> X </span>
</h3>
</div>
<div class="modal-body">
<div class="SettingMenuRows">
Phylogenetic Spacing (px per unit): <input id="phyloVizTreeSeparation" type="text" value="250" size="10" displayLabel="Phylogenetic Separation"> (50-2500)
</div>
<div class="SettingMenuRows">
Vertical Spacing (px): <input type="text" id="phyloVizTreeLeafHeight" value="18" size="10" displayLabel="Vertical Spacing"> (5-30)
</div>
<div class="SettingMenuRows">
Font Size (px): <input type="text" id="phyloVizTreeFontSize" value="12" size="4" displayLabel="Font Size"> (5-20)
</div>
</div>
<div class="modal-footer">
<button id="phylovizResetSettingsBtn" class="PhyloVizFloatLeft" > Reset </button>
<button id="phylovizApplySettingsBtn" class="PhyloVizFloatRight" > Apply </button>
</div>
</div>
<div class="Panel" id="FloatingMenu" style="display: None;">
<h2>PhyloViz (<a onclick="displayHelp()" href="javascript:void(0);">?</a>)</h2>
<div style="display: none;">
<h2>Summary of Interactions and Functions:</h2>
<div class="hint">1. Expansion of Nodes: click or option-click to expand or collapse</div>
<div class="hint">2. Zooming and translation: mousewheel, buttons, click and drag, double click. Reset</div>
<div class="hint">3. Tooltip: Displays "Name and Size" on mouseOver on nodes</div>
<div class="hint">4. Minimap: Currently displays an exact but scaled down replicate of the tree, orange bounding box is correct for linear only<br/>
Can be switched on or off</div>
<div class="hint">5. Changing Layouts: Able to change between circular and linear layouts.</div>
</div>
<h5>Scaling & Rotation:</h5>
<button id="phylovizZoomInBtn" class="" > + </button>
<button id="phylovizZoomOutBtn" class="" > - </button>
<h5>Translation:</h5>
<button id="phylovizTranslateUpBtn" > Up </button>
<button id="phylovizTranslateDownBtn" > Down </button>
<br/>
<button id="phylovizTranslateLeftBtn" > Left </button>
<button id="phylovizTranslateRightBtn" > Right </button>
<h5>Others:</h5>
<button id="phylovizResetBtn" > Reset Zoom/Translate </button>
<button id="phylovizSaveBtn" > Save vizualization </button>
<button id="phylovizOpenSettingsBtn" > Settings </button>
</div>
<div id="PhyloViz" >
</div>
<script type="text/javascript">
function initPhyloViz(data, config) {
var phyloviz;
// -- Initialization code |-->
phyloviz = new PhylovizView({
data: data,
layout : "Linear",
config : config
});
// -- Render viz. --
phyloviz.render();
}
$(function firstVizLoad(){ // calls when viz is loaded for the first time
var config = JSON.parse( '${ h.to_json_string( config )}');
var data = JSON.parse('${h.to_json_string(data)}');
initPhyloViz(data, config);
});
</script>
</%def>
+32 -25
View File
@@ -60,34 +60,41 @@
%endif
<div class="toolSectionPad"></div>
<div class="toolSectionTitle">
Repositories
All Repositories
</div>
<div class="toolSectionBody">
<div class="toolSectionBg">
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_categories', webapp='community' )}">Browse by category</a>
</div>
%if trans.user:
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='my_repositories', webapp='community' )}">Browse my repositories</a>
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_invalid_tools', cntrller='repository', webapp='community' )}">Browse my invalid tools</a>
</div>
%endif
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_categories', webapp='community' )}">Browse by category</a>
</div>
%if trans.user:
<div class="toolSectionPad"></div>
<div class="toolSectionTitle">
My Repositories and Tools
</div>
</div>
<div class="toolSectionBody">
<div class="toolSectionBg">
<div class="toolTitle">
%if trans.user:
<a target="galaxy_main" href="${h.url_for( controller='repository', action='create_repository', webapp='community' )}">Create new repository</a>
%else:
<a target="galaxy_main" href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login to create a repository</a>
%endif
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='repositories_i_own', webapp='community' )}">Repositories I own</a>
</div>
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_repositories', operation='writable_repositories', webapp='community' )}">My writable repositories</a>
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_invalid_tools', cntrller='repository', webapp='community' )}">My invalid tools</a>
</div>
<div class="toolSectionPad"></div>
<div class="toolSectionTitle">
Available Actions
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='repository', action='create_repository', webapp='community' )}">Create new repository</a>
</div>
%else:
<div class="toolSectionPad"></div>
<div class="toolSectionTitle">
Available Actions
</div>
<div class="toolTitle">
<a target="galaxy_main" href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login to create a repository</a>
</div>
%endif
</div>
</div>
</div>
+2 -1
View File
@@ -4,6 +4,7 @@
import socket, urllib, sys, os
from galaxy import eggs #eggs needs to be imported so that galaxy.util can find docutils egg...
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util import get_charset_from_http_headers
import galaxy.model # need to import model before sniff to resolve a circular import dependency
from galaxy.datatypes import sniff
from galaxy.datatypes.registry import Registry
@@ -92,7 +93,7 @@ def __main__():
stop_err( 'The size of the data (%d bytes) you have requested exceeds the maximum allowed (%d bytes) on this server.' % ( file_size, max_file_size ) )
#do sniff stream for multi_byte
try:
cur_filename, is_multi_byte = sniff.stream_to_open_named_file( page, os.open( cur_filename, os.O_WRONLY | os.O_CREAT ), cur_filename )
cur_filename, is_multi_byte = sniff.stream_to_open_named_file( page, os.open( cur_filename, os.O_WRONLY | os.O_CREAT ), cur_filename, source_encoding=get_charset_from_http_headers( page.headers ) )
except Exception, e:
stop_err( 'Unable to fetch %s:\n%s' % ( cur_URL, e ) )
+2 -1
View File
@@ -90,7 +90,8 @@ def add_file( dataset, registry, json_file, output_path ):
if dataset.type == 'url':
try:
temp_name, dataset.is_multi_byte = sniff.stream_to_file( urllib.urlopen( dataset.path ), prefix='url_paste' )
page = urllib.urlopen( dataset.path ) #page will be .close()ed by sniff methods
temp_name, dataset.is_multi_byte = sniff.stream_to_file( page, prefix='url_paste', source_encoding=util.get_charset_from_http_headers( page.headers ) )
except Exception, e:
file_err( 'Unable to fetch %s\n%s' % ( dataset.path, str( e ) ), dataset, json_file )
return