changeset 53:4217bb9cf1d3

depend on python 3; fix internal links with multiple iterations
author Jan Kanis <jan.code@jankanis.nl>
date Mon, 26 May 2014 13:07:13 +0200
parents d6c7b5de2833
children 6d96d48a10f8
files blast2html.html.jinja blast2html.py blast2html.xml tool_dependencies.xml
diffstat 4 files changed, 63 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/blast2html.html.jinja	Wed May 21 18:39:51 2014 +0200
+++ b/blast2html.html.jinja	Mon May 26 13:07:13 2014 +0200
@@ -126,7 +126,7 @@
       max-width: 50em;
       text-align: left;
       height: 2.8em;
-      overflow-y: hidden;
+      overflow: hidden;
       }
 
       div.legend {
@@ -400,7 +400,7 @@
         <h1>Queries</h1>
 
         {% for result in blast.BlastOutput_iterations.Iteration %}
-        <div class=indexentry><a href="#match{{result['Iteration_iter-num']}}">
+        <div class=indexentry><a href="#match{{result|nodeid}}">
             {% set hits = result|hits %}
             {{result['Iteration_query-ID']}}: {{result['Iteration_query-def']}}
             ({{result|len}} letters, {{hits|length}} hits)
@@ -412,7 +412,7 @@
 
       {% for result in blast.BlastOutput_iterations.Iteration %}
       
-      <section class=match id=match{{result['Iteration_iter-num']}}>
+      <section class=match id=match{{result|nodeid}}>
       
         <h1>Nucleotide Sequence ({{result|len}} letters)</h1>
 
@@ -444,7 +444,7 @@
           <div class=grey>
             <h3 class=centered>Distribution of {{result|length}} Blast Hits on the Query Sequence</h3>
 
-            <div class=defline id=defline{{result['Iteration_iter-num']}}>
+            <div class=defline id=defline{{result|nodeid}}>
               Mouse-over to show defline and scores, click to show alignments
             </div>
 
@@ -482,9 +482,9 @@
 
                 {% for line in result|match_colors %}
                 <a class=matchresult
-                   href="{{line.link}}"
-                   onmouseover='document.getElementById("defline{{result['Iteration_iter-num']}}").innerHTML="{{line.defline|js_string_escape}}"'
-                   onmouseout='document.getElementById("defline{{result['Iteration_iter-num']}}").innerHTML="Mouse-over to show defline and scores, click to show alignments"'
+                   href="#hit{{line.hit|nodeid}}"
+                   onmouseover='document.getElementById("defline{{result|nodeid}}").innerHTML="{{line.defline|js_string_escape}}"'
+                   onmouseout='document.getElementById("defline{{result|nodeid}}").innerHTML="Mouse-over to show defline and scores, click to show alignments"'
                    title="{{line.defline}}">
                   <div class="matchrow graphicrow">
                     {% for hit in line.colors %}
@@ -521,9 +521,9 @@
                 </tr>
                 {% for hit in result|hit_info %}
                 <tr>
-                  <td><div><a href="#hit{{hit.link_id}}"
+                  <td><div><a href="#hit{{hit.hit|nodeid}}"
                               title="{{hit.title}}"
-                              id="description{{hit.link_id}}">
+                              id="description{{hit.hit|nodeid}}">
                         {{hit.title}}
                   </a></div></td>
                   <td>{{hit.maxscore}}</td>
@@ -546,10 +546,10 @@
 
           <div class=grey><div class=white>
               {% for hit in hits %}
-              <div class=alignment id=hit{{hit.Hit_num}}>
+              <div class=alignment id=hit{{hit|nodeid}}>
 
                 <div class=linkheader>
-                  <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>
+                  <div class=right><a href="#description{{hit|nodeid}}">Descriptions</a></div>
                   <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>
                   <a class=linkheader href="{{genelink(hit|hitid, 'graph')}}">Graphics</a>
                 </div>
@@ -564,11 +564,11 @@
                 </div>
 
                 {% if hit|othertitles|length %}
-                <a class=showmoretitles onclick="toggle_visibility('moretitles{{hit.Hit_num|js_string_escape}}'); return false;" href=''>
+                <a class=showmoretitles onclick="toggle_visibility('moretitles{{hit|nodeid|js_string_escape}}'); return false;" href=''>
                   See {{hit|othertitles|length}} more title(s)
                 </a>
 
-                <div class=moretitles id=moretitles{{hit.Hit_num}} style="display: none">
+                <div class=moretitles id=moretitles{{hit|nodeid}} style="display: none">
                   {% for title in hit|othertitles %}
                   <div class=title>
                     <p class=hittitle>{{title.title}}</p>
@@ -581,7 +581,7 @@
                 {% endif %}
 
                 {% for hsp in hit.Hit_hsps.Hsp %}
-                <div class=hotspot>
+                <div class=hotspot id=hotspot{{hsp|nodeid}}>
                   <p class=range>
                     <span class=range>Range {{hsp.Hsp_num}}: {{hsp['Hsp_hit-from']}} to {{hsp['Hsp_hit-to']}}</span>
                     <a class=range href="{{genelink(hit|hitid, 'genbank', hsp)}}">GenBank</a>
--- a/blast2html.py	Wed May 21 18:39:51 2014 +0200
+++ b/blast2html.py	Mon May 26 13:07:13 2014 +0200
@@ -1,7 +1,5 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-#
-# Actually runs under either python 2 or 3
 
 # Copyright The Hyve B.V. 2014
 # License: GPL version 3 or higher
@@ -13,7 +11,6 @@
 import warnings
 from os import path
 from itertools import repeat
-import six
 import argparse
 from lxml import objectify
 import jinja2
@@ -23,7 +20,7 @@
 _filters = {}
 def filter(func_or_name):
     "Decorator to register a function as filter in the current jinja environment"
-    if isinstance(func_or_name, six.string_types):
+    if isinstance(func_or_name, str):
         def inner(func):
             _filters[func_or_name] = func.__name__
             return func
@@ -99,9 +96,25 @@
     elif node.tag == 'Iteration':
         return int(node['Iteration_query-len'])
     raise Exception("Unknown XML node type: "+node.tag)
-        
 
 @filter
+def nodeid(node):
+    id = []
+    if node.tag == 'Hsp':
+        id.insert(0, node.Hsp_num.text)
+        node = node.getparent().getparent()
+        assert node.tag == 'Hit'
+    if node.tag == 'Hit':
+        id.insert(0, node.Hit_num.text)
+        node = node.getparent().getparent()
+        assert node.tag == 'Iteration'
+    if node.tag == 'Iteration':
+        id.insert(0, node['Iteration_iter-num'].text)
+        return '-'.join(id)
+    raise ValueError("The nodeid filter can only be applied to Hsp, Hit or Iteration nodes in a BlastXML document")
+
+    
+@filter
 def asframe(frame):
     if frame == 1:
         return 'Plus'
@@ -174,7 +187,7 @@
         self.templatename = templatename
 
         self.blast = objectify.parse(self.input).getroot()
-        self.loader = jinja2.FileSystemLoader(searchpath=templatedir, encoding='utf-8')
+        self.loader = jinja2.FileSystemLoader(searchpath=templatedir)
         self.environment = jinja2.Environment(loader=self.loader,
                                               lstrip_blocks=True, trim_blocks=True, autoescape=True)
 
@@ -238,7 +251,7 @@
                 count = 1
             matches.append((count * percent_multiplier, self.colors[last] if last != 255 else 'transparent'))
 
-            yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))
+            yield dict(colors=matches, hit=hit, defline=firsttitle(hit))
 
     @filter
     def queryscale(self, result):
@@ -269,8 +282,7 @@
                 return (float(hsp[path]) for hsp in hsps)
 
             yield dict(hit = hit,
-                       title = firsttitle(hit),
-                       link_id = hit.Hit_num,
+                      title = firsttitle(hit),
                        maxscore = "{:.1f}".format(max(hsp_val('Hsp_bit-score'))),
                        totalscore = "{:.1f}".format(sum(hsp_val('Hsp_bit-score'))),
                        cover = "{:.0%}".format(cover_count / query_length),
@@ -288,16 +300,16 @@
     input_group = parser.add_mutually_exclusive_group(required=True)
     input_group.add_argument('positional_arg', metavar='INPUT', nargs='?', type=argparse.FileType(mode='r'),
                              help='The input Blast XML file, same as -i/--input')
-    input_group.add_argument('-i', '--input', type=argparse.FileType(mode='r', encoding='utf-8'), 
+    input_group.add_argument('-i', '--input', type=argparse.FileType(mode='r'), 
                              help='The input Blast XML file')
-    parser.add_argument('-o', '--output', type=argparse.FileType(mode='w', encoding='utf-8'), default=sys.stdout,
+    parser.add_argument('-o', '--output', type=argparse.FileType(mode='w'), default=sys.stdout,
                         help='The output html file')
     # We just want the file name here, so jinja can open the file
     # itself. But it is easier to just use a FileType so argparse can
     # handle the errors. This introduces a small race condition when
     # jinja later tries to re-open the template file, but we don't
     # care too much.
-    parser.add_argument('--template', type=argparse.FileType(mode='r', encoding='utf-8'), default=default_template,
+    parser.add_argument('--template', type=argparse.FileType(mode='r'), default=default_template,
                         help='The template file to use. Defaults to blast_html.html.jinja')
 
     args = parser.parse_args()
--- a/blast2html.xml	Wed May 21 18:39:51 2014 +0200
+++ b/blast2html.xml	Mon May 26 13:07:13 2014 +0200
@@ -1,13 +1,11 @@
-<tool id="blast2html" name="blast2html" version="0.0.7">
+<tool id="blast2html" name="blast2html" version="0.0.8">
     
     <description>Convert BLAST XML to HTML</description>
     
     <requirements>
       <requirement type="package" version="v0.0.18">blast_datatypes</requirement>
-      <requirement name="package" version="1.3.0">python_six</requirement>
-      <requirement name="package" version="1.2.1">argparse</requirement>
-      <requirement name="package" version="2.2.3">lxml</requirement>
-      <requirement name="package" version="2.7.2">jinja2</requirement>
+      <requirement name="package" version="3.4.1">python3</requirement>
+      <requirement name="package" version="0.1">blast2html_venv</requirement>
     </requirements>
     
     <command interpreter="python">blast2html.py -i "${input}" -o "${output}"</command>
--- a/tool_dependencies.xml	Wed May 21 18:39:51 2014 +0200
+++ b/tool_dependencies.xml	Mon May 26 13:07:13 2014 +0200
@@ -15,29 +15,12 @@
                 toolshed="http://toolshed.g2.bx.psu.edu" changeset_revision="039b04adcfee" />
   </package>
 
-  <package name="argparse" version="1.2.1">
-    <install version="1.0">
-      <actions>
-        <action type="setup_virtualenv">
-          argparse == 1.2.1
-        </action>
-      </actions>
-    </install>
-    <readme>The Python argparse library. This is included in the stdlib for python 2.7 and up, but not for 2.6</readme>
+  <package name="python3" version="3.4.1">
+    <repository name="package_python3_4" owner="jankanis" prior_installation_required="True"
+                toolshed="http://toolshed.g2.bx.psu.edu" changeset_revision="9dbfc4fcef0a" />
   </package>
-
-  <package name="python_six" version="1.3.0">
-    <install version="1.0">
-      <actions>
-        <action type="setup_virtualenv">
-          six >= 1.3.0
-        </action>
-      </actions>
-    </install>
-    <readme>The Python six library for python 2/3 compatibility</readme>
-  </package>
-
-  <package name="lxml" version="2.2.3">
+  
+  <package name="blast2html_venv" version="0.1">
     <install version="1.0">
       <actions>
         <action type="set_environment_for_install">
@@ -53,29 +36,27 @@
                       toolshed="http://toolshed.g2.bx.psu.edu" changeset_revision="039b04adcfee">
             <package name="libxslt" version="1.1.28" />
           </repository>
+          <repository name="python3" owner="jankanis">
+            <package name="python3" version="3.4.1" />
+          </repository>
         </action>
-	<action type="shell_command">
-	  echo '*******************************************************************************'
-	  echo $PATH
-	  echo xslt-config at: `which -a xslt-config`
-	</action>
-        <action type="setup_virtualenv">
-          lxml == 2.2.3
+        
+        <action type="shell_command">
+          # Unset any saved environment settings from parent virtual
+          # environments, e.g. for python 2 or if Galaxy itself is running
+          # from within a virtual environment.
+          unset _OLD_VIRTUAL_PATH; unset _OLD_VIRTUAL_PYTHONHOME
+          pyvenv blast2html_venv
+          . blast2html_venv/bin/activate
+          pip3 install lxml jinja2
+        </action>
+        
+        <action type="set_environment">
+          <environment_variable name="PYTHONPATH" action="prepend_to">$INSTALL_DIR/lib/python3.4/site-packages</environment_variable>
         </action>
       </actions>
     </install>
-    <readme>The Python lxml library</readme>
-  </package>
-
-  <package name="jinja2" version="2.7.2">
-    <install version="1.0">
-      <actions>
-        <action type="setup_virtualenv">
-          jinja2 == 2.7.2
-        </action>
-      </actions>
-    </install>
-    <readme>The Jinja 2 template engine for Python</readme>
+    <readme>A Python 3 virtual environment that includes the python packages blast2html depends on, which are lxml and jinja2.</readme>
   </package>
 
 </tool_dependency>