galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
February 2013
- 2 participants
- 189 discussions
5 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9e36b0deaf6f/
changeset: 9e36b0deaf6f
user: james_taylor
date: 2013-02-02 20:10:47
summary: Added tag release_2013.01.13 for changeset a4113cc1cb5e
affected #: 1 file
diff -r a4113cc1cb5eaa68091c9a73375f00555b66dd11 -r 9e36b0deaf6f8e8477d317ef35bb601e04b27c07 .hgtags
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,1 @@
+a4113cc1cb5eaa68091c9a73375f00555b66dd11 release_2013.01.13
https://bitbucket.org/galaxy/galaxy-central/commits/3299529e0fe8/
changeset: 3299529e0fe8
user: james_taylor
date: 2013-02-02 21:37:21
summary: jobs bugfix: fix syntax error in light weight runner init
affected #: 1 file
diff -r a4113cc1cb5eaa68091c9a73375f00555b66dd11 -r 3299529e0fe8ca1206c949801024c15b83375016 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -229,7 +229,7 @@
nworkers = app.config.local_job_queue_workers
log.info( "starting workers" )
for i in range( nworkers ):
- worker = threading.Thread( ( name="LwrJobRunner.thread-%d" % i ), target=self.run_next )
+ worker = threading.Thread( name=( "LwrJobRunner.thread-%d" % i ), target=self.run_next )
worker.setDaemon( True )
worker.start()
self.threads.append( worker )
https://bitbucket.org/galaxy/galaxy-central/commits/6f9d36b726b0/
changeset: 6f9d36b726b0
branch: stable
user: james_taylor
date: 2013-02-02 21:38:58
summary: merge
affected #: 1 file
diff -r 7cc74b8605b253606ddeeb29587bc8803eee0597 -r 6f9d36b726b01606433808fc781df710f755c71b lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -229,7 +229,7 @@
nworkers = app.config.local_job_queue_workers
log.info( "starting workers" )
for i in range( nworkers ):
- worker = threading.Thread( ( name="LwrJobRunner.thread-%d" % i ), target=self.run_next )
+ worker = threading.Thread( name=( "LwrJobRunner.thread-%d" % i ), target=self.run_next )
worker.setDaemon( True )
worker.start()
self.threads.append( worker )
https://bitbucket.org/galaxy/galaxy-central/commits/2bb814d0b723/
changeset: 2bb814d0b723
user: james_taylor
date: 2013-02-02 21:43:56
summary: merge
affected #: 1 file
diff -r 3299529e0fe8ca1206c949801024c15b83375016 -r 2bb814d0b723233b1b085ca4b639e4fa8aa612f3 .hgtags
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,1 @@
+a4113cc1cb5eaa68091c9a73375f00555b66dd11 release_2013.01.13
https://bitbucket.org/galaxy/galaxy-central/commits/fa1a29005dca/
changeset: fa1a29005dca
user: james_taylor
date: 2013-02-02 21:46:31
summary: merge
affected #: 239 files
Diff not available.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/73bb36c4ee3b/
changeset: 73bb36c4ee3b
branch: next-stable
user: dan
date: 2013-02-02 00:13:05
summary: Fix for workflow parameters.
affected #: 2 files
diff -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 -r 73bb36c4ee3b34c3ca32ed830b4f91df79b971c5 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -42,6 +42,8 @@
log = logging.getLogger( __name__ )
+WORKFLOW_PARAMETER_REGULAR_EXPRESSION = re.compile( '''\$\{.+?\}''' )
+
# These determine stdio-based error levels from matching on regular expressions
# and exit codes. They are meant to be used comparatively, such as showing
# that warning < fatal. This is really meant to just be an enum.
@@ -2123,16 +2125,16 @@
return params_to_strings( self.inputs, params, app )
def params_from_strings( self, params, app, ignore_errors=False ):
return params_from_strings( self.inputs, params, app, ignore_errors )
- def check_and_update_param_values( self, values, trans, update_values=True ):
+ def check_and_update_param_values( self, values, trans, update_values=True, allow_workflow_parameters=False ):
"""
Check that all parameters have values, and fill in with default
values where necessary. This could be called after loading values
from a database in case new parameters have been added.
"""
messages = {}
- self.check_and_update_param_values_helper( self.inputs, values, trans, messages, update_values=update_values )
+ self.check_and_update_param_values_helper( self.inputs, values, trans, messages, update_values=update_values, allow_workflow_parameters=allow_workflow_parameters )
return messages
- def check_and_update_param_values_helper( self, inputs, values, trans, messages, context=None, prefix="", update_values=True ):
+ def check_and_update_param_values_helper( self, inputs, values, trans, messages, context=None, prefix="", update_values=True, allow_workflow_parameters=False ):
"""
Recursive helper for `check_and_update_param_values_helper`
"""
@@ -2177,8 +2179,13 @@
else:
# Regular tool parameter, no recursion needed
try:
+ check_param = True
+ if allow_workflow_parameters and isinstance( values[ input.name ], basestring ):
+ if WORKFLOW_PARAMETER_REGULAR_EXPRESSION.search( values[ input.name ] ):
+ check_param = False
#this will fail when a parameter's type has changed to a non-compatible one: e.g. conditional group changed to dataset input
- input.value_from_basic( input.value_to_basic( values[ input.name ], trans.app ), trans.app, ignore_errors=False )
+ if check_param:
+ input.value_from_basic( input.value_to_basic( values[ input.name ], trans.app ), trans.app, ignore_errors=False )
except:
messages[ input.name ] = "Value no longer valid for '%s%s', replaced with default" % ( prefix, input.label )
if update_values:
diff -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 -r 73bb36c4ee3b34c3ca32ed830b4f91df79b971c5 lib/galaxy/workflow/modules.py
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -352,7 +352,7 @@
self.errors = errors or None
def check_and_update_state( self ):
- return self.tool.check_and_update_param_values( self.state.inputs, self.trans )
+ return self.tool.check_and_update_param_values( self.state.inputs, self.trans, allow_workflow_parameters=True )
def add_dummy_datasets( self, connections=None):
if connections:
https://bitbucket.org/galaxy/galaxy-central/commits/bb6a6347e50b/
changeset: bb6a6347e50b
user: dan
date: 2013-02-02 00:13:27
summary: Merged from next-stable
affected #: 2 files
diff -r 132aeb4a4b96de744d20de6f368d197d59e7b9e3 -r bb6a6347e50be301026039e157cd952462ed0683 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -42,6 +42,8 @@
log = logging.getLogger( __name__ )
+WORKFLOW_PARAMETER_REGULAR_EXPRESSION = re.compile( '''\$\{.+?\}''' )
+
# These determine stdio-based error levels from matching on regular expressions
# and exit codes. They are meant to be used comparatively, such as showing
# that warning < fatal. This is really meant to just be an enum.
@@ -2123,16 +2125,16 @@
return params_to_strings( self.inputs, params, app )
def params_from_strings( self, params, app, ignore_errors=False ):
return params_from_strings( self.inputs, params, app, ignore_errors )
- def check_and_update_param_values( self, values, trans, update_values=True ):
+ def check_and_update_param_values( self, values, trans, update_values=True, allow_workflow_parameters=False ):
"""
Check that all parameters have values, and fill in with default
values where necessary. This could be called after loading values
from a database in case new parameters have been added.
"""
messages = {}
- self.check_and_update_param_values_helper( self.inputs, values, trans, messages, update_values=update_values )
+ self.check_and_update_param_values_helper( self.inputs, values, trans, messages, update_values=update_values, allow_workflow_parameters=allow_workflow_parameters )
return messages
- def check_and_update_param_values_helper( self, inputs, values, trans, messages, context=None, prefix="", update_values=True ):
+ def check_and_update_param_values_helper( self, inputs, values, trans, messages, context=None, prefix="", update_values=True, allow_workflow_parameters=False ):
"""
Recursive helper for `check_and_update_param_values_helper`
"""
@@ -2177,8 +2179,13 @@
else:
# Regular tool parameter, no recursion needed
try:
+ check_param = True
+ if allow_workflow_parameters and isinstance( values[ input.name ], basestring ):
+ if WORKFLOW_PARAMETER_REGULAR_EXPRESSION.search( values[ input.name ] ):
+ check_param = False
#this will fail when a parameter's type has changed to a non-compatible one: e.g. conditional group changed to dataset input
- input.value_from_basic( input.value_to_basic( values[ input.name ], trans.app ), trans.app, ignore_errors=False )
+ if check_param:
+ input.value_from_basic( input.value_to_basic( values[ input.name ], trans.app ), trans.app, ignore_errors=False )
except:
messages[ input.name ] = "Value no longer valid for '%s%s', replaced with default" % ( prefix, input.label )
if update_values:
diff -r 132aeb4a4b96de744d20de6f368d197d59e7b9e3 -r bb6a6347e50be301026039e157cd952462ed0683 lib/galaxy/workflow/modules.py
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -352,7 +352,7 @@
self.errors = errors or None
def check_and_update_state( self ):
- return self.tool.check_and_update_param_values( self.state.inputs, self.trans )
+ return self.tool.check_and_update_param_values( self.state.inputs, self.trans, allow_workflow_parameters=True )
def add_dummy_datasets( self, connections=None):
if connections:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/350d6d927398/
changeset: 350d6d927398
user: jgoecks
date: 2013-02-01 23:56:55
summary: Move hda state counting out of grid and into a function in UsesHistory mixin.
affected #: 2 files
diff -r d166635a6f1528fdc233641e223dab2b78ba755d -r 350d6d92739872c661049e0418600d8c1f0d23fa lib/galaxy/web/base/controller.py
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -691,6 +691,8 @@
"""Get a History from the database by id, verifying ownership."""
history = self.get_object( trans, id, 'History', check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted )
return self.security_check( trans, history, check_ownership, check_accessible )
+
+
def get_history_datasets( self, trans, history, show_deleted=False, show_hidden=False, show_purged=False ):
""" Returns history's datasets. """
query = trans.sa_session.query( trans.model.HistoryDatasetAssociation ) \
@@ -705,6 +707,42 @@
query = query.filter( trans.model.Dataset.purged == False )
return query.all()
+ def get_hda_state_counts( self, trans, history, include_deleted=False, include_hidden=False ):
+ """
+ Returns a dictionary with state counts for history's HDAs. Key is a
+ dataset state, value is the number of states in that count.
+ """
+
+ # Build query to get (state, count) pairs.
+ cols_to_select = [ trans.app.model.Dataset.table.c.state, func.count( '*' ) ]
+ from_obj = trans.app.model.HistoryDatasetAssociation.table.join( trans.app.model.Dataset.table )
+
+ conditions = [ trans.app.model.HistoryDatasetAssociation.table.c.history_id == history.id ]
+ if not include_deleted:
+ # Only count datasets that have not been deleted.
+ conditions.append( trans.app.model.HistoryDatasetAssociation.table.c.deleted == False )
+ if not include_hidden:
+ # Only count datasets that are visible.
+ conditions.append( trans.app.model.HistoryDatasetAssociation.table.c.visible == True )
+
+ group_by = trans.app.model.Dataset.table.c.state
+ query = select( columns=cols_to_select,
+ from_obj=from_obj,
+ whereclause=and_( *conditions ),
+ group_by=group_by )
+
+ # Initialize count dict with all states.
+ state_count_dict = {}
+ for k, state in trans.app.model.Dataset.states.items():
+ state_count_dict[ state ] = 0
+
+ # Process query results, adding to count dict.
+ for row in trans.sa_session.execute( query ):
+ state, count = row
+ state_count_dict[ state ] = count
+
+ return state_count_dict
+
class UsesFormDefinitionsMixin:
"""Mixin for controllers that use Galaxy form objects."""
def get_all_forms( self, trans, all_versions=False, filter=None, form_type='All' ):
diff -r d166635a6f1528fdc233641e223dab2b78ba755d -r 350d6d92739872c661049e0418600d8c1f0d23fa lib/galaxy/webapps/galaxy/controllers/history.py
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -25,26 +25,10 @@
class HistoryListGrid( grids.Grid ):
# Custom column types
- class DatasetsByStateColumn( grids.GridColumn ):
+ class DatasetsByStateColumn( grids.GridColumn, UsesHistoryMixin ):
def get_value( self, trans, grid, history ):
- # Build query to get (state, count) pairs.
- cols_to_select = [ trans.app.model.Dataset.table.c.state, func.count( '*' ) ]
- from_obj = trans.app.model.HistoryDatasetAssociation.table.join( trans.app.model.Dataset.table )
- where_clause = and_( trans.app.model.HistoryDatasetAssociation.table.c.history_id == history.id,
- trans.app.model.HistoryDatasetAssociation.table.c.deleted == False,
- trans.app.model.HistoryDatasetAssociation.table.c.visible == True,
- )
- group_by = trans.app.model.Dataset.table.c.state
- query = select( columns=cols_to_select,
- from_obj=from_obj,
- whereclause=where_clause,
- group_by=group_by )
-
- # Process results.
- state_count_dict = {}
- for row in trans.sa_session.execute( query ):
- state, count = row
- state_count_dict[ state ] = count
+ state_count_dict = self.get_hda_state_counts( trans, history )
+
rval = []
for state in ( 'ok', 'running', 'queued', 'error' ):
count = state_count_dict.get( state, 0 )
@@ -53,12 +37,16 @@
else:
rval.append( '' )
return rval
+
+
class HistoryListNameColumn( NameColumn ):
def get_link( self, trans, grid, history ):
link = None
if not history.deleted:
link = dict( operation="Switch", id=history.id, use_panels=grid.use_panels )
return link
+
+
class DeletedColumn( grids.DeletedColumn ):
def get_value( self, trans, grid, history ):
if history == trans.history:
@@ -75,6 +63,7 @@
query = query.order_by( self.model_class.table.c.purged.desc(), self.model_class.table.c.update_time.desc() )
return query
+
# Grid definition
title = "Saved Histories"
model_class = model.History
https://bitbucket.org/galaxy/galaxy-central/commits/132aeb4a4b96/
changeset: 132aeb4a4b96
user: jgoecks
date: 2013-02-02 00:06:09
summary: Resolve imports.
affected #: 1 file
diff -r 350d6d92739872c661049e0418600d8c1f0d23fa -r 132aeb4a4b96de744d20de6f368d197d59e7b9e3 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -4,7 +4,8 @@
import logging, os, string, shutil, urllib, re, socket
from cgi import escape, FieldStorage
from galaxy import util, datatypes, jobs, web, util
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseAPIController, UsesHistoryMixin
+from galaxy.web import url_for
from galaxy.util.sanitize_html import sanitize_html
from galaxy.model.orm import *
import galaxy.datatypes
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d166635a6f15/
changeset: d166635a6f15
user: jgoecks
date: 2013-02-01 23:49:25
summary: Remove debugging statement.
affected #: 1 file
diff -r 1314557d09836855394cf67d6c805fd86a85f4cc -r d166635a6f1528fdc233641e223dab2b78ba755d static/scripts/libs/jquery/select2.js
--- a/static/scripts/libs/jquery/select2.js
+++ b/static/scripts/libs/jquery/select2.js
@@ -2346,8 +2346,6 @@
return (value === undefined) ? this : value;
};
- console.log( "TEST", $.fn.select2 );
-
// plugin defaults, accessible to users
$.fn.select2.defaults = {
width: "copy",
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: james_taylor: core: add PasteDeploy dependency back, used inside Paste#httpserver
by Bitbucket 01 Feb '13
by Bitbucket 01 Feb '13
01 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/1314557d0983/
changeset: 1314557d0983
user: james_taylor
date: 2013-02-01 23:30:58
summary: core: add PasteDeploy dependency back, used inside Paste#httpserver
affected #: 3 files
diff -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb -r 1314557d09836855394cf67d6c805fd86a85f4cc eggs.ini
--- a/eggs.ini
+++ b/eggs.ini
@@ -47,6 +47,7 @@
NoseHTML = 0.4.1
NoseTestDiff = 0.1
Paste = 1.6
+PasteDeploy = 1.3.3
pexpect = 2.4
python_openid = 2.2.5
python_daemon = 1.5.5
diff -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb -r 1314557d09836855394cf67d6c805fd86a85f4cc lib/galaxy/util/pastescript/__init__.py
--- /dev/null
+++ b/lib/galaxy/util/pastescript/__init__.py
@@ -0,0 +1,3 @@
+"""
+Command for loading and serving wsgi apps taken from PasteScript
+"""
diff -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb -r 1314557d09836855394cf67d6c805fd86a85f4cc scripts/paster.py
--- a/scripts/paster.py
+++ b/scripts/paster.py
@@ -27,8 +27,7 @@
import tempfile
pkg_resources.require( "Paste" )
-#pkg_resources.require( "PasteScript" )
-#pkg_resources.require( "PasteDeploy" )
+pkg_resources.require( "PasteDeploy" )
from galaxy.util.pastescript import serve
serve.run()
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: james_taylor: Use the select2 library for large select boxes rather than our custom
by Bitbucket 01 Feb '13
by Bitbucket 01 Feb '13
01 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/91e6f09a02ea/
changeset: 91e6f09a02ea
user: james_taylor
date: 2013-02-01 22:49:16
summary: Use the select2 library for large select boxes rather than our custom
implementation based on jquery autocomplete.
affected #: 11 files
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/images/select2-spinner.gif
Binary file static/images/select2-spinner.gif has changed
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/images/select2.png
Binary file static/images/select2.png has changed
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/june_2007_style/base.less
--- a/static/june_2007_style/base.less
+++ b/static/june_2007_style/base.less
@@ -4,6 +4,8 @@
@import "fontawesome/font-awesome.less";
+@import "select2.less";
+
// Mixins
.unselectable {
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/june_2007_style/blue/base.css
--- a/static/june_2007_style/blue/base.css
+++ b/static/june_2007_style/blue/base.css
@@ -824,7 +824,69 @@
.fa-icon-github-alt:before{content:"\f113";}
.fa-icon-folder-close-alt:before{content:"\f114";}
.fa-icon-folder-open-alt:before{content:"\f115";}
-.unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}
+.select2-container{position:relative;display:inline-block;zoom:1;*display:inline;vertical-align:top;}
+.select2-container,.select2-drop,.select2-search,.select2-search input{-moz-box-sizing:border-box;-ms-box-sizing:border-box;-webkit-box-sizing:border-box;-khtml-box-sizing:border-box;box-sizing:border-box;}
+.select2-container .select2-choice{background-color:#fff;background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, #ffffff));background-image:-webkit-linear-gradient(center bottom, #eeeeee 0%, #ffffff 50%);background-image:-moz-linear-gradient(center bottom, #eeeeee 0%, #ffffff 50%);background-image:-o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);background-image:-ms-linear-gradient(top, #eeeeee 0%, #ffffff 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);background-image:linear-gradient(top, #eeeeee 0%, #ffffff 50%);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #aaa;display:block;overflow:hidden;white-space:nowrap;position:relative;height:26px;line-height:26px;padding:0 0 0 8px;color:#444;text-decoration:none;}
+.select2-container.select2-drop-above .select2-choice{border-bottom-color:#aaa;-webkit-border-radius:0px 0px 4px 4px;-moz-border-radius:0px 0px 4px 4px;border-radius:0px 0px 4px 4px;background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, #ffffff));background-image:-webkit-linear-gradient(center bottom, #eeeeee 0%, #ffffff 90%);background-image:-moz-linear-gradient(center bottom, #eeeeee 0%, #ffffff 90%);background-image:-o-linear-gradient(bottom, #eeeeee 0%, #ffffff 90%);background-image:-ms-linear-gradient(top, #eeeeee 0%, #ffffff 90%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);background-image:linear-gradient(top, #eeeeee 0%, #ffffff 90%);}
+.select2-container .select2-choice span{margin-right:26px;display:block;overflow:hidden;white-space:nowrap;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis;text-overflow:ellipsis;}
+.select2-container .select2-choice abbr{display:block;position:absolute;right:26px;top:8px;width:12px;height:12px;font-size:1px;background:url('../images/select2.png') right top no-repeat;cursor:pointer;text-decoration:none;border:0;outline:0;}
+.select2-container .select2-choice abbr:hover{background-position:right -11px;cursor:pointer;}
+.select2-drop{background:#fff;color:#000;border:1px solid #aaa;border-top:0;position:absolute;top:100%;-webkit-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-o-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);z-index:9999;width:100%;margin-top:-1px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
+.select2-drop.select2-drop-above{-webkit-border-radius:4px 4px 0px 0px;-moz-border-radius:4px 4px 0px 0px;border-radius:4px 4px 0px 0px;margin-top:1px;border-top:1px solid #aaa;border-bottom:0;-webkit-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);-o-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);}
+.select2-container .select2-choice div{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background:#ccc;background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, #cccccc), color-stop(0.6, #eeeeee));background-image:-webkit-linear-gradient(center bottom, #cccccc 0%, #eeeeee 60%);background-image:-moz-linear-gradient(center bottom, #cccccc 0%, #eeeeee 60%);background-image:-o-linear-gradient(bottom, #cccccc 0%, #eeeeee 60%);background-image:-ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#eeeeee', GradientType=0);background-image:linear-gradient(top, #cccccc 0%, #eeeeee 60%);border-left:1px solid #aaa;position:absolute;right:0;top:0;display:block;height:100%;width:18px;}
+.select2-container .select2-choice div b{background:url('../images/select2.png') no-repeat 0 1px;display:block;width:100%;height:100%;}
+.select2-search{display:inline-block;white-space:nowrap;z-index:10000;min-height:26px;width:100%;margin:0;padding-left:4px;padding-right:4px;}
+.select2-search-hidden{display:block;position:absolute;left:-10000px;}
+.select2-search input{background:#ffffff url('../images/select2.png') no-repeat 100% -22px;background:url('../images/select2.png') no-repeat 100% -22px,-webkit-gradient(linear, left bottom, left top, color-stop(0.85, #ffffff), color-stop(0.99, #eeeeee));background:url('../images/select2.png') no-repeat 100% -22px,-webkit-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-moz-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-o-linear-gradient(bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,linear-gradient(top, #ffffff 85%, #eeeeee 99%);padding:4px 20px 4px 5px;outline:0;border:1px solid #aaa;font-family:sans-serif;font-size:1em;width:100%;margin:0;height:auto !important;min-height:26px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-radius:0;-moz-border-radius:0;-webkit-border-radius:0;}
+.select2-drop.select2-drop-above .select2-search input{margin-top:4px;}
+.select2-search input.select2-active{background:#ffffff url('../images/select2-spinner.gif') no-repeat 100%;background:url('../images/select2-spinner.gif') no-repeat 100%,-webkit-gradient(linear, left bottom, left top, color-stop(0.85, #ffffff), color-stop(0.99, #eeeeee));background:url('../images/select2-spinner.gif') no-repeat 100%,-webkit-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2-spinner.gif') no-repeat 100%,-moz-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2-spinner.gif') no-repeat 100%,-o-linear-gradient(bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2-spinner.gif') no-repeat 100%,-ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);background:url('../images/select2-spinner.gif') no-repeat 100%,linear-gradient(top, #ffffff 85%, #eeeeee 99%);}
+.select2-container-active .select2-choice,.select2-container-active .select2-choices{-webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);-o-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);box-shadow:0 0 5px rgba(0, 0, 0, 0.3);border:1px solid #5897fb;outline:none;}
+.select2-dropdown-open .select2-choice{border:1px solid #aaa;border-bottom-color:transparent;-webkit-box-shadow:0 1px 0 #fff inset;-moz-box-shadow:0 1px 0 #fff inset;-o-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background-color:#eee;background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, #ffffff), color-stop(0.5, #eeeeee));background-image:-webkit-linear-gradient(center bottom, #ffffff 0%, #eeeeee 50%);background-image:-moz-linear-gradient(center bottom, #ffffff 0%, #eeeeee 50%);background-image:-o-linear-gradient(bottom, #ffffff 0%, #eeeeee 50%);background-image:-ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);background-image:linear-gradient(top, #ffffff 0%, #eeeeee 50%);-webkit-border-bottom-left-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-bottomright:0;border-bottom-left-radius:0;border-bottom-right-radius:0;}
+.select2-dropdown-open .select2-choice div{background:transparent;border-left:none;}
+.select2-dropdown-open .select2-choice div b{background-position:-18px 1px;}
+.select2-results{margin:4px 4px 4px 0;padding:0 0 0 4px;position:relative;overflow-x:hidden;overflow-y:auto;max-height:200px;}
+.select2-results ul.select2-result-sub{margin:0 0 0 0;}
+.select2-results ul.select2-result-sub>li .select2-result-label{padding-left:20px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:40px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:60px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:80px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:100px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:110px;}
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub>li .select2-result-label{padding-left:120px;}
+.select2-results li{list-style:none;display:list-item;}
+.select2-results li.select2-result-with-children>.select2-result-label{font-weight:bold;}
+.select2-results .select2-result-label{padding:3px 7px 4px;margin:0;cursor:pointer;}
+.select2-results .select2-highlighted{background:#3875d7;color:#fff;}
+.select2-results li em{background:#feffde;font-style:normal;}
+.select2-results .select2-highlighted em{background:transparent;}
+.select2-results .select2-no-results,.select2-results .select2-searching,.select2-results .select2-selection-limit{background:#f4f4f4;display:list-item;}
+.select2-results .select2-disabled{display:none;}
+.select2-more-results.select2-active{background:#f4f4f4 url('spinner.gif') no-repeat 100%;}
+.select2-more-results{background:#f4f4f4;display:list-item;}
+.select2-container.select2-container-disabled .select2-choice{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default;}
+.select2-container.select2-container-disabled .select2-choice div{background-color:#f4f4f4;background-image:none;border-left:0;}
+.select2-container-multi .select2-choices{background-color:#fff;background-image:-webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));background-image:-webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);background-image:-moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);background-image:-o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);background-image:-ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);background-image:linear-gradient(top, #eeeeee 1%, #ffffff 15%);border:1px solid #aaa;margin:0;padding:0;cursor:text;overflow:hidden;height:auto !important;height:1%;position:relative;}
+.select2-container-multi .select2-choices{min-height:26px;}
+.select2-container-multi.select2-container-active .select2-choices{-webkit-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);-o-box-shadow:0 0 5px rgba(0, 0, 0, 0.3);box-shadow:0 0 5px rgba(0, 0, 0, 0.3);border:1px solid #5897fb;outline:none;}
+.select2-container-multi .select2-choices li{float:left;list-style:none;}
+.select2-container-multi .select2-choices .select2-search-field{white-space:nowrap;margin:0;padding:0;}
+.select2-container-multi .select2-choices .select2-search-field input{color:#666;background:transparent !important;font-family:sans-serif;font-size:100%;height:15px;padding:5px;margin:1px 0;outline:0;border:0;-webkit-box-shadow:none;-moz-box-shadow:none;-o-box-shadow:none;box-shadow:none;}
+.select2-container-multi .select2-choices .select2-search-field input.select2-active{background:#ffffff url('spinner.gif') no-repeat 100% !important;}
+.select2-default{color:#999 !important;}
+.select2-container-multi .select2-choices .select2-search-choice{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background-color:#e4e4e4;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0);background-image:-webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));background-image:-webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);background-image:-moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);background-image:-o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);background-image:-ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);background-image:linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);-webkit-box-shadow:0 0 2px #ffffff inset,0 1px 0 rgba(0, 0, 0, 0.05);-moz-box-shadow:0 0 2px #ffffff inset,0 1px 0 rgba(0, 0, 0, 0.05);box-shadow:0 0 2px #ffffff inset,0 1px 0 rgba(0, 0, 0, 0.05);color:#333;border:1px solid #aaaaaa;line-height:13px;padding:3px 5px 3px 18px;margin:3px 0 3px 5px;position:relative;cursor:default;}
+.select2-container-multi .select2-choices .select2-search-choice span{cursor:default;}
+.select2-container-multi .select2-choices .select2-search-choice-focus{background:#d4d4d4;}
+.select2-search-choice-close{display:block;position:absolute;right:3px;top:4px;width:12px;height:13px;font-size:1px;background:url('../images/select2.png') right top no-repeat;outline:none;}
+.select2-container-multi .select2-search-choice-close{left:3px;}
+.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover{background-position:right -11px;}
+.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close{background-position:right -11px;}
+.select2-container-multi.select2-container-disabled .select2-choices{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default;}
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice{background-image:none;background-color:#f4f4f4;border:1px solid #ddd;padding:3px 5px 3px 5px;}
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close{display:none;}
+.select2-result-selectable .select2-match,.select2-result-unselectable .select2-result-selectable .select2-match{text-decoration:underline;}
+.select2-result-unselectable .select2-match{text-decoration:none;}
+.select2-offscreen{position:absolute;left:-10000px;}
+@media only screen and (-webkit-min-device-pixel-ratio:1.5){.select2-search input,.select2-search-choice-close,.select2-container .select2-choice abbr,.select2-container .select2-choice div b{background-image:url(select2x2.png) !important;background-repeat:no-repeat !important;background-size:60px 40px !important;} .select2-search input{background-position:100% -21px !important;}}.unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}
.parent-width{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;*width:90%;}
.clear{*zoom:1;}.clear:before,.clear:after{display:table;content:"";line-height:0;}
.clear:after{clear:both;}
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/june_2007_style/select2.less
--- /dev/null
+++ b/static/june_2007_style/select2.less
@@ -0,0 +1,524 @@
+/*
+Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
+*/
+.select2-container {
+ position: relative;
+ display: inline-block;
+ /* inline-block for ie7 */
+ zoom: 1;
+ *display: inline;
+ vertical-align: top;
+}
+
+.select2-container,
+.select2-drop,
+.select2-search,
+.select2-search input{
+ /*
+ Force border-box so that % widths fit the parent
+ container without overlap because of margin/padding.
+
+ More Info : http://www.quirksmode.org/css/box.html
+ */
+ -moz-box-sizing: border-box; /* firefox */
+ -ms-box-sizing: border-box; /* ie */
+ -webkit-box-sizing: border-box; /* webkit */
+ -khtml-box-sizing: border-box; /* konqueror */
+ box-sizing: border-box; /* css3 */
+}
+
+.select2-container .select2-choice {
+ background-color: #fff;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
+ background-image: -ms-linear-gradient(top, #eeeeee 0%, #ffffff 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#ffffff', GradientType = 0);
+ background-image: linear-gradient(top, #eeeeee 0%, #ffffff 50%);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #aaa;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ position: relative;
+ height: 26px;
+ line-height: 26px;
+ padding: 0 0 0 8px;
+ color: #444;
+ text-decoration: none;
+}
+
+.select2-container.select2-drop-above .select2-choice
+{
+ border-bottom-color: #aaa;
+ -webkit-border-radius:0px 0px 4px 4px;
+ -moz-border-radius:0px 0px 4px 4px;
+ border-radius:0px 0px 4px 4px;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
+ background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
+ background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
+}
+
+.select2-container .select2-choice span {
+ margin-right: 26px;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ -o-text-overflow: ellipsis;
+ -ms-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+}
+
+.select2-container .select2-choice abbr {
+ display: block;
+ position: absolute;
+ right: 26px;
+ top: 8px;
+ width: 12px;
+ height: 12px;
+ font-size: 1px;
+ background: url('../images/select2.png') right top no-repeat;
+ cursor: pointer;
+ text-decoration: none;
+ border:0;
+ outline: 0;
+}
+.select2-container .select2-choice abbr:hover {
+ background-position: right -11px;
+ cursor: pointer;
+}
+
+.select2-drop {
+ background: #fff;
+ color: #000;
+ border: 1px solid #aaa;
+ border-top: 0;
+ position: absolute;
+ top: 100%;
+ -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ -o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ z-index: 9999;
+ width:100%;
+ margin-top:-1px;
+
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.select2-drop.select2-drop-above {
+ -webkit-border-radius: 4px 4px 0px 0px;
+ -moz-border-radius: 4px 4px 0px 0px;
+ border-radius: 4px 4px 0px 0px;
+ margin-top:1px;
+ border-top: 1px solid #aaa;
+ border-bottom: 0;
+
+ -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+ -moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+ -o-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+ box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+}
+
+.select2-container .select2-choice div {
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ background: #ccc;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
+ background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+ background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+ background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
+ background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#cccccc', endColorstr = '#eeeeee', GradientType = 0);
+ background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+ border-left: 1px solid #aaa;
+ position: absolute;
+ right: 0;
+ top: 0;
+ display: block;
+ height: 100%;
+ width: 18px;
+}
+
+.select2-container .select2-choice div b {
+ background: url('../images/select2.png') no-repeat 0 1px;
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.select2-search {
+ display: inline-block;
+ white-space: nowrap;
+ z-index: 10000;
+ min-height: 26px;
+ width: 100%;
+ margin: 0;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+.select2-search-hidden {
+ display: block;
+ position: absolute;
+ left: -10000px;
+}
+
+.select2-search input {
+ background: #fff url('../images/select2.png') no-repeat 100% -22px;
+ background: url('../images/select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+ background: url('../images/select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+ background: url('../images/select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+ padding: 4px 20px 4px 5px;
+ outline: 0;
+ border: 1px solid #aaa;
+ font-family: sans-serif;
+ font-size: 1em;
+ width:100%;
+ margin:0;
+ height:auto !important;
+ min-height: 26px;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ border-radius: 0;
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+}
+
+.select2-drop.select2-drop-above .select2-search input
+{
+ margin-top:4px;
+}
+
+.select2-search input.select2-active {
+ background: #fff url('../images/select2-spinner.gif') no-repeat 100%;
+ background: url('../images/select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+ background: url('../images/select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+ background: url('../images/select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+ background: url('../images/select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+}
+
+
+.select2-container-active .select2-choice,
+.select2-container-active .select2-choices {
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ -moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
+ -o-box-shadow : 0 0 5px rgba(0,0,0,.3);
+ box-shadow : 0 0 5px rgba(0,0,0,.3);
+ border: 1px solid #5897fb;
+ outline: none;
+}
+
+.select2-dropdown-open .select2-choice {
+ border: 1px solid #aaa;
+ border-bottom-color: transparent;
+ -webkit-box-shadow: 0 1px 0 #fff inset;
+ -moz-box-shadow : 0 1px 0 #fff inset;
+ -o-box-shadow : 0 1px 0 #fff inset;
+ box-shadow : 0 1px 0 #fff inset;
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
+ background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+ background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+ background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
+ background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
+ background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+ -webkit-border-bottom-left-radius : 0;
+ -webkit-border-bottom-right-radius: 0;
+ -moz-border-radius-bottomleft : 0;
+ -moz-border-radius-bottomright: 0;
+ border-bottom-left-radius : 0;
+ border-bottom-right-radius: 0;
+}
+
+.select2-dropdown-open .select2-choice div {
+ background: transparent;
+ border-left: none;
+}
+.select2-dropdown-open .select2-choice div b {
+ background-position: -18px 1px;
+}
+
+/* results */
+.select2-results {
+ margin: 4px 4px 4px 0;
+ padding: 0 0 0 4px;
+ position: relative;
+ overflow-x: hidden;
+ overflow-y: auto;
+ max-height: 200px;
+}
+
+.select2-results ul.select2-result-sub {
+ margin: 0 0 0 0;
+}
+
+.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
+.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
+
+.select2-results li {
+ list-style: none;
+ display: list-item;
+}
+
+.select2-results li.select2-result-with-children > .select2-result-label {
+ font-weight: bold;
+}
+
+.select2-results .select2-result-label {
+ padding: 3px 7px 4px;
+ margin: 0;
+ cursor: pointer;
+}
+
+.select2-results .select2-highlighted {
+ background: #3875d7;
+ color: #fff;
+}
+.select2-results li em {
+ background: #feffde;
+ font-style: normal;
+}
+.select2-results .select2-highlighted em {
+ background: transparent;
+}
+.select2-results .select2-no-results,
+.select2-results .select2-searching,
+.select2-results .select2-selection-limit {
+ background: #f4f4f4;
+ display: list-item;
+}
+
+/*
+disabled look for already selected choices in the results dropdown
+.select2-results .select2-disabled.select2-highlighted {
+ color: #666;
+ background: #f4f4f4;
+ display: list-item;
+ cursor: default;
+}
+.select2-results .select2-disabled {
+ background: #f4f4f4;
+ display: list-item;
+ cursor: default;
+}
+*/
+.select2-results .select2-disabled {
+ display: none;
+}
+
+.select2-more-results.select2-active {
+ background: #f4f4f4 url('spinner.gif') no-repeat 100%;
+}
+
+.select2-more-results {
+ background: #f4f4f4;
+ display: list-item;
+}
+
+/* disabled styles */
+
+.select2-container.select2-container-disabled .select2-choice {
+ background-color: #f4f4f4;
+ background-image: none;
+ border: 1px solid #ddd;
+ cursor: default;
+}
+
+.select2-container.select2-container-disabled .select2-choice div {
+ background-color: #f4f4f4;
+ background-image: none;
+ border-left: 0;
+}
+
+
+/* multiselect */
+
+.select2-container-multi .select2-choices {
+ background-color: #fff;
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
+ background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ border: 1px solid #aaa;
+ margin: 0;
+ padding: 0;
+ cursor: text;
+ overflow: hidden;
+ height: auto !important;
+ height: 1%;
+ position: relative;
+}
+
+.select2-container-multi .select2-choices {
+ min-height: 26px;
+}
+
+.select2-container-multi.select2-container-active .select2-choices {
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ -moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
+ -o-box-shadow : 0 0 5px rgba(0,0,0,.3);
+ box-shadow : 0 0 5px rgba(0,0,0,.3);
+ border: 1px solid #5897fb;
+ outline: none;
+}
+.select2-container-multi .select2-choices li {
+ float: left;
+ list-style: none;
+}
+.select2-container-multi .select2-choices .select2-search-field {
+ white-space: nowrap;
+ margin: 0;
+ padding: 0;
+}
+
+.select2-container-multi .select2-choices .select2-search-field input {
+ color: #666;
+ background: transparent !important;
+ font-family: sans-serif;
+ font-size: 100%;
+ height: 15px;
+ padding: 5px;
+ margin: 1px 0;
+ outline: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow : none;
+ -o-box-shadow : none;
+ box-shadow : none;
+}
+
+.select2-container-multi .select2-choices .select2-search-field input.select2-active {
+ background: #fff url('spinner.gif') no-repeat 100% !important;
+}
+
+.select2-default {
+ color: #999 !important;
+}
+
+.select2-container-multi .select2-choices .select2-search-choice {
+ -webkit-border-radius: 3px;
+ -moz-border-radius : 3px;
+ border-radius : 3px;
+ -moz-background-clip : padding;
+ -webkit-background-clip: padding-box;
+ background-clip : padding-box;
+ background-color: #e4e4e4;
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 );
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+ -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+ box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+ color: #333;
+ border: 1px solid #aaaaaa;
+ line-height: 13px;
+ padding: 3px 5px 3px 18px;
+ margin: 3px 0 3px 5px;
+ position: relative;
+ cursor: default;
+}
+.select2-container-multi .select2-choices .select2-search-choice span {
+ cursor: default;
+}
+.select2-container-multi .select2-choices .select2-search-choice-focus {
+ background: #d4d4d4;
+}
+
+.select2-search-choice-close {
+ display: block;
+ position: absolute;
+ right: 3px;
+ top: 4px;
+ width: 12px;
+ height: 13px;
+ font-size: 1px;
+ background: url('../images/select2.png') right top no-repeat;
+ outline: none;
+}
+
+.select2-container-multi .select2-search-choice-close {
+ left: 3px;
+}
+
+
+.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
+ background-position: right -11px;
+}
+.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
+ background-position: right -11px;
+}
+
+/* disabled styles */
+
+.select2-container-multi.select2-container-disabled .select2-choices{
+ background-color: #f4f4f4;
+ background-image: none;
+ border: 1px solid #ddd;
+ cursor: default;
+}
+
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
+ background-image: none;
+ background-color: #f4f4f4;
+ border: 1px solid #ddd;
+ padding: 3px 5px 3px 5px;
+}
+
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {
+ display: none;
+}
+/* end multiselect */
+
+.select2-result-selectable .select2-match,
+.select2-result-unselectable .select2-result-selectable .select2-match { text-decoration: underline; }
+.select2-result-unselectable .select2-match { text-decoration: none; }
+
+.select2-offscreen { position: absolute; left: -10000px; }
+
+/* Retina-ize icons */
+
+@media only screen and (-webkit-min-device-pixel-ratio: 1.5) {
+ .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b {
+ background-image: url(select2x2.png) !important;
+ background-repeat: no-repeat !important;
+ background-size: 60px 40px !important;
+ }
+ .select2-search input {
+ background-position: 100% -21px !important;
+ }
+}
\ No newline at end of file
diff -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 -r 91e6f09a02ea1ce044628c65ed51a72d6331a1bb static/scripts/galaxy.base.js
--- a/static/scripts/galaxy.base.js
+++ b/static/scripts/galaxy.base.js
@@ -241,8 +241,9 @@
// Replace select box with a text input box + autocomplete.
function replace_big_select_inputs(min_length, max_length, select_elts) {
- // To do replace, jQuery's autocomplete plugin must be loaded.
- if (!jQuery().autocomplete) {
+ // To do replace, the select2 plugin must be loaded.
+
+ if (!jQuery.fn.select2) {
return;
}
@@ -255,7 +256,7 @@
}
var select_elts = select_elts || $('select');
-
+
select_elts.each( function() {
var select_elt = $(this);
// Make sure that options is within range.
@@ -263,131 +264,21 @@
if ( (num_options < min_length) || (num_options > max_length) ) {
return;
}
-
- // Skip multi-select because widget cannot handle multi-select.
- if (select_elt.attr('multiple') === 'multiple') {
- return;
- }
-
+
if (select_elt.hasClass("no-autocomplete")) {
return;
}
+
+ /* Replaced jQuery.autocomplete with select2, notes:
+ * - multiple selects are supported
+ * - the original element is updated with the value, convert_to_values should not be needed
+ * - events are fired when updating the original element, so refresh_on_change should just work
+ *
+ * - should we still sort dbkey fields here?
+ */
- // Replace select with text + autocomplete.
- var start_value = select_elt.attr('value');
-
- // Set up text input + autocomplete element.
- var text_input_elt = $("<input type='text' class='text-and-autocomplete-select'></input>");
- text_input_elt.attr('size', 40);
- text_input_elt.attr('name', select_elt.attr('name'));
- text_input_elt.attr('id', select_elt.attr('id'));
- text_input_elt.click( function() {
- // Show all. Also provide note that load is happening since this can be slow.
- var cur_value = $(this).val();
- $(this).val('Loading...');
- $(this).showAllInCache();
- $(this).val(cur_value);
- $(this).select();
- });
+ select_elt.select2( { width: "resolve" } );
- // Get options for select for autocomplete.
- var select_options = [];
- var select_mapping = {};
- select_elt.children('option').each( function() {
- // Get text, value for option.
- var text = $(this).text();
- var value = $(this).attr('value');
-
- // Set options and mapping. Mapping is (i) [from text to value] AND (ii) [from value to value]. This
- // enables a user to type the value directly rather than select the text that represents the value.
- select_options.push( text );
- select_mapping[ text ] = value;
- select_mapping[ value ] = value;
-
- // If this is the start value, set value of input element.
- if ( value == start_value ) {
- text_input_elt.attr('value', text);
- }
- });
-
- // Set initial text if it's empty.
- if ( start_value === '' || start_value === '?') {
- text_input_elt.attr('value', 'Click to Search or Select');
- }
-
- // Sort dbkey options list only.
- if (select_elt.attr('name') == 'dbkey') {
- select_options = select_options.sort(naturalSort);
- }
-
- // Do autocomplete.
- var autocomplete_options = { selectFirst: false, autoFill: false, mustMatch: false, matchContains: true, max: max_length, minChars : 0, hideForLessThanMinChars : false };
- text_input_elt.autocomplete(select_options, autocomplete_options);
-
- // Replace select with text input.
- select_elt.replaceWith(text_input_elt);
-
- // Set trigger to replace text with value when element's form is submitted. If text doesn't correspond to value, default to start value.
- var submit_hook = function() {
- // Try to convert text to value.
- var cur_value = text_input_elt.attr('value');
- var new_value = select_mapping[cur_value];
- if (new_value !== null && new_value !== undefined) {
- text_input_elt.attr('value', new_value);
- } else {
- // If there is a non-empty start value, use that; otherwise unknown.
- if (start_value !== "") {
- text_input_elt.attr('value', start_value);
- } else {
- // This is needed to make the DB key work.
- text_input_elt.attr('value', '?');
- }
- }
- };
- text_input_elt.parents('form').submit( function() { submit_hook(); } );
-
- // Add custom event so that other objects can execute name --> value conversion whenever they want.
- $(document).bind("convert_to_values", function() { submit_hook(); } );
-
- // If select is refresh on change, mirror this behavior.
- if (select_elt.attr('refresh_on_change') == 'true') {
- // Get refresh vals.
- var ref_on_change_vals = select_elt.attr('refresh_on_change_values'),
- last_selected_value = select_elt.attr("last_selected_value");
- if (ref_on_change_vals !== undefined) {
- ref_on_change_vals = ref_on_change_vals.split(',');
- }
- // Function that attempts to refresh based on the value in the text element.
- var try_refresh_fn = function() {
- // Get new value and see if it can be matched.
- var new_value = select_mapping[text_input_elt.attr('value')];
- if (last_selected_value !== new_value && new_value !== null && new_value !== undefined) {
- if (ref_on_change_vals !== undefined && $.inArray(new_value, ref_on_change_vals) === -1 && $.inArray(last_selected_value, ref_on_change_vals) === -1) {
- return;
- }
- text_input_elt.attr('value', new_value);
- $(window).trigger("refresh_on_change");
- text_input_elt.parents('form').submit();
- }
- };
-
- // Attempt refresh if (a) result event fired by autocomplete (indicating autocomplete occurred) or (b) on keyup (in which
- // case a user may have manually entered a value that needs to be refreshed).
- text_input_elt.bind("result", try_refresh_fn);
- text_input_elt.keyup( function(e) {
- if (e.keyCode === 13) { // Return key
- try_refresh_fn();
- }
- });
-
- // Disable return key so that it does not submit the form automatically. This is done because element should behave like a
- // select (enter = select), not text input (enter = submit form).
- text_input_elt.keydown( function(e) {
- if (e.keyCode === 13) { // Return key
- return false;
- }
- });
- }
});
}
@@ -729,6 +620,7 @@
};
$(document).ready( function() {
+
$("select[refresh_on_change='true']").change( function() {
var select_field = $(this),
select_val = select_field.val(),
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
01 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/57ca00cec386/
changeset: 57ca00cec386
user: james_taylor
date: 2013-01-30 21:26:00
summary: changeset: 8701:c3ac9849dd1b
branch: unstable
tag: tip
user: James Taylor <james(a)jamestaylor.org>
summary: core: pull the pieces of PasteScript we use into galaxy.util.pastescript. Remove all dependencies on PasteScript and PasteDeploy. Begin stripping down the serve command so we can start to enhance it for our needs.
affected #: 15 files
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 eggs.ini
--- a/eggs.ini
+++ b/eggs.ini
@@ -47,8 +47,6 @@
NoseHTML = 0.4.1
NoseTestDiff = 0.1
Paste = 1.6
-PasteDeploy = 1.3.3
-PasteScript = 1.7.3
pexpect = 2.4
python_openid = 2.2.5
python_daemon = 1.5.5
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/condor.py
--- a/lib/galaxy/jobs/runners/condor.py
+++ b/lib/galaxy/jobs/runners/condor.py
@@ -4,7 +4,7 @@
from galaxy import model
from galaxy.jobs.runners import BaseJobRunner
-from paste.deploy.converters import asbool
+from galaxy.util import asbool
import pkg_resources
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/drmaa.py
--- a/lib/galaxy/jobs/runners/drmaa.py
+++ b/lib/galaxy/jobs/runners/drmaa.py
@@ -10,8 +10,6 @@
from galaxy import model
from galaxy.jobs.runners import BaseJobRunner
-from paste.deploy.converters import asbool
-
import pkg_resources
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/pbs.py
--- a/lib/galaxy/jobs/runners/pbs.py
+++ b/lib/galaxy/jobs/runners/pbs.py
@@ -7,8 +7,6 @@
from galaxy.util.bunch import Bunch
from galaxy.jobs.runners import BaseJobRunner
-from paste.deploy.converters import asbool
-
import pkg_resources
egg_message = """
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -333,6 +333,21 @@
# No luck, return empty string
return ''
+# asbool implementation pulled from PasteDeploy
+truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1'])
+falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0'])
+def asbool(obj):
+ if isinstance(obj, basestring):
+ obj = obj.strip().lower()
+ if obj in truthy:
+ return True
+ elif obj in falsy:
+ return False
+ else:
+ raise ValueError("String is not true/false: %r" % obj)
+ return bool(obj)
+
+
def string_as_bool( string ):
if str( string ).lower() in ( 'true', 'yes', 'on' ):
return True
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/pastescript/loadwsgi.py
--- /dev/null
+++ b/lib/galaxy/util/pastescript/loadwsgi.py
@@ -0,0 +1,828 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# Mostly taken from PasteDeploy and stripped down for Galaxy
+
+from __future__ import with_statement
+
+import inspect
+import os
+import sys
+import re
+
+import pkg_resources
+
+__all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig']
+
+# ---- from paste.deploy.compat --------------------------------------
+
+"""Python 2<->3 compatibility module"""
+
+def print_(template, *args, **kwargs):
+ template = str(template)
+ if args:
+ template = template % args
+ elif kwargs:
+ template = template % kwargs
+ sys.stdout.writelines(template)
+
+if sys.version_info < (3, 0):
+ basestring = basestring
+ from ConfigParser import ConfigParser
+ from urllib import unquote
+ iteritems = lambda d: d.iteritems()
+ dictkeys = lambda d: d.keys()
+
+ def reraise(t, e, tb):
+ exec('raise t, e, tb', dict(t=t, e=e, tb=tb))
+else:
+ basestring = str
+ from configparser import ConfigParser
+ from urllib.parse import unquote
+ iteritems = lambda d: d.items()
+ dictkeys = lambda d: list(d.keys())
+
+ def reraise(t, e, tb):
+ exec('raise e from tb', dict(e=e, tb=tb))
+
+# ---- from paste.deploy.util ----------------------------------------
+
+def fix_type_error(exc_info, callable, varargs, kwargs):
+ """
+ Given an exception, this will test if the exception was due to a
+ signature error, and annotate the error with better information if
+ so.
+
+ Usage::
+
+ try:
+ val = callable(*args, **kw)
+ except TypeError:
+ exc_info = fix_type_error(None, callable, args, kw)
+ raise exc_info[0], exc_info[1], exc_info[2]
+ """
+ if exc_info is None:
+ exc_info = sys.exc_info()
+ if (exc_info[0] != TypeError
+ or str(exc_info[1]).find('arguments') == -1
+ or getattr(exc_info[1], '_type_error_fixed', False)):
+ return exc_info
+ exc_info[1]._type_error_fixed = True
+ argspec = inspect.formatargspec(*inspect.getargspec(callable))
+ args = ', '.join(map(_short_repr, varargs))
+ if kwargs and args:
+ args += ', '
+ if kwargs:
+ kwargs = kwargs.items()
+ kwargs.sort()
+ args += ', '.join(['%s=...' % n for n, v in kwargs])
+ gotspec = '(%s)' % args
+ msg = '%s; got %s, wanted %s' % (exc_info[1], gotspec, argspec)
+ exc_info[1].args = (msg,)
+ return exc_info
+
+
+def _short_repr(v):
+ v = repr(v)
+ if len(v) > 12:
+ v = v[:8] + '...' + v[-4:]
+ return v
+
+
+def fix_call(callable, *args, **kw):
+ """
+ Call ``callable(*args, **kw)`` fixing any type errors that come out.
+ """
+ try:
+ val = callable(*args, **kw)
+ except TypeError:
+ exc_info = fix_type_error(None, callable, args, kw)
+ reraise(*exc_info)
+ return val
+
+
+def lookup_object(spec):
+ """
+ Looks up a module or object from a some.module:func_name specification.
+ To just look up a module, omit the colon and everything after it.
+ """
+ parts, target = spec.split(':') if ':' in spec else (spec, None)
+ module = __import__(parts)
+
+ for part in parts.split('.')[1:] + ([target] if target else []):
+ module = getattr(module, part)
+
+ return module
+
+# ---- from paste.deploy.loadwsgi ------------------------------------
+
+############################################################
+## Utility functions
+############################################################
+
+
+def import_string(s):
+ return pkg_resources.EntryPoint.parse("x=" + s).load(False)
+
+
+def _aslist(obj):
+ """
+ Turn object into a list; lists and tuples are left as-is, None
+ becomes [], and everything else turns into a one-element list.
+ """
+ if obj is None:
+ return []
+ elif isinstance(obj, (list, tuple)):
+ return obj
+ else:
+ return [obj]
+
+
+def _flatten(lst):
+ """
+ Flatten a nested list.
+ """
+ if not isinstance(lst, (list, tuple)):
+ return [lst]
+ result = []
+ for item in lst:
+ result.extend(_flatten(item))
+ return result
+
+
+class NicerConfigParser(ConfigParser):
+
+ def __init__(self, filename, *args, **kw):
+ ConfigParser.__init__(self, *args, **kw)
+ self.filename = filename
+ if hasattr(self, '_interpolation'):
+ self._interpolation = self.InterpolateWrapper(self._interpolation)
+
+ read_file = getattr(ConfigParser, 'read_file', ConfigParser.readfp)
+
+ def defaults(self):
+ """Return the defaults, with their values interpolated (with the
+ defaults dict itself)
+
+ Mainly to support defaults using values such as %(here)s
+ """
+ defaults = ConfigParser.defaults(self).copy()
+ for key, val in iteritems(defaults):
+ defaults[key] = self.get('DEFAULT', key) or val
+ return defaults
+
+ def _interpolate(self, section, option, rawval, vars):
+ # Python < 3.2
+ try:
+ return ConfigParser._interpolate(
+ self, section, option, rawval, vars)
+ except Exception:
+ e = sys.exc_info()[1]
+ args = list(e.args)
+ args[0] = 'Error in file %s: %s' % (self.filename, e)
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+ class InterpolateWrapper(object):
+ # Python >= 3.2
+ def __init__(self, original):
+ self._original = original
+
+ def __getattr__(self, name):
+ return getattr(self._original, name)
+
+ def before_get(self, parser, section, option, value, defaults):
+ try:
+ return self._original.before_get(parser, section, option,
+ value, defaults)
+ except Exception:
+ e = sys.exc_info()[1]
+ args = list(e.args)
+ args[0] = 'Error in file %s: %s' % (parser.filename, e)
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+
+############################################################
+## Object types
+############################################################
+
+
+class _ObjectType(object):
+
+ name = None
+ egg_protocols = None
+ config_prefixes = None
+
+ def __init__(self):
+ # Normalize these variables:
+ self.egg_protocols = [_aslist(p) for p in _aslist(self.egg_protocols)]
+ self.config_prefixes = [_aslist(p) for p in _aslist(self.config_prefixes)]
+
+ def __repr__(self):
+ return '<%s protocols=%r prefixes=%r>' % (
+ self.name, self.egg_protocols, self.config_prefixes)
+
+ def invoke(self, context):
+ assert context.protocol in _flatten(self.egg_protocols)
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+
+
+class _App(_ObjectType):
+
+ name = 'application'
+ egg_protocols = ['paste.app_factory', 'paste.composite_factory',
+ 'paste.composit_factory']
+ config_prefixes = [['app', 'application'], ['composite', 'composit'],
+ 'pipeline', 'filter-app']
+
+ def invoke(self, context):
+ if context.protocol in ('paste.composit_factory',
+ 'paste.composite_factory'):
+ return fix_call(context.object,
+ context.loader, context.global_conf,
+ **context.local_conf)
+ elif context.protocol == 'paste.app_factory':
+ return fix_call(context.object, context.global_conf, **context.local_conf)
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+APP = _App()
+
+
+class _Filter(_ObjectType):
+ name = 'filter'
+ egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']]
+ config_prefixes = ['filter']
+
+ def invoke(self, context):
+ if context.protocol == 'paste.filter_factory':
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+ elif context.protocol == 'paste.filter_app_factory':
+ def filter_wrapper(wsgi_app):
+ # This should be an object, so it has a nicer __repr__
+ return fix_call(context.object,
+ wsgi_app, context.global_conf,
+ **context.local_conf)
+ return filter_wrapper
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+FILTER = _Filter()
+
+
+class _Server(_ObjectType):
+ name = 'server'
+ egg_protocols = [['paste.server_factory', 'paste.server_runner']]
+ config_prefixes = ['server']
+
+ def invoke(self, context):
+ if context.protocol == 'paste.server_factory':
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+ elif context.protocol == 'paste.server_runner':
+ def server_wrapper(wsgi_app):
+ # This should be an object, so it has a nicer __repr__
+ return fix_call(context.object,
+ wsgi_app, context.global_conf,
+ **context.local_conf)
+ return server_wrapper
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+SERVER = _Server()
+
+
+# Virtual type: (@@: There's clearly something crufty here;
+# this probably could be more elegant)
+class _PipeLine(_ObjectType):
+ name = 'pipeline'
+
+ def invoke(self, context):
+ app = context.app_context.create()
+ filters = [c.create() for c in context.filter_contexts]
+ filters.reverse()
+ for filter in filters:
+ app = filter(app)
+ return app
+
+PIPELINE = _PipeLine()
+
+
+class _FilterApp(_ObjectType):
+ name = 'filter_app'
+
+ def invoke(self, context):
+ next_app = context.next_context.create()
+ filter = context.filter_context.create()
+ return filter(next_app)
+
+FILTER_APP = _FilterApp()
+
+
+class _FilterWith(_App):
+ name = 'filtered_with'
+
+ def invoke(self, context):
+ filter = context.filter_context.create()
+ filtered = context.next_context.create()
+ if context.next_context.object_type is APP:
+ return filter(filtered)
+ else:
+ # filtering a filter
+ def composed(app):
+ return filter(filtered(app))
+ return composed
+
+FILTER_WITH = _FilterWith()
+
+
+############################################################
+## Loaders
+############################################################
+
+
+def loadapp(uri, name=None, **kw):
+ return loadobj(APP, uri, name=name, **kw)
+
+
+def loadfilter(uri, name=None, **kw):
+ return loadobj(FILTER, uri, name=name, **kw)
+
+
+def loadserver(uri, name=None, **kw):
+ return loadobj(SERVER, uri, name=name, **kw)
+
+
+def appconfig(uri, name=None, relative_to=None, global_conf=None):
+ context = loadcontext(APP, uri, name=name,
+ relative_to=relative_to,
+ global_conf=global_conf)
+ return context.config()
+
+_loaders = {}
+
+
+def loadobj(object_type, uri, name=None, relative_to=None,
+ global_conf=None):
+ context = loadcontext(
+ object_type, uri, name=name, relative_to=relative_to,
+ global_conf=global_conf)
+ return context.create()
+
+
+def loadcontext(object_type, uri, name=None, relative_to=None,
+ global_conf=None):
+ if '#' in uri:
+ if name is None:
+ uri, name = uri.split('#', 1)
+ else:
+ # @@: Ignore fragment or error?
+ uri = uri.split('#', 1)[0]
+ if name is None:
+ name = 'main'
+ if ':' not in uri:
+ raise LookupError("URI has no scheme: %r" % uri)
+ scheme, path = uri.split(':', 1)
+ scheme = scheme.lower()
+ if scheme not in _loaders:
+ raise LookupError(
+ "URI scheme not known: %r (from %s)"
+ % (scheme, ', '.join(_loaders.keys())))
+ return _loaders[scheme](
+ object_type,
+ uri, path, name=name, relative_to=relative_to,
+ global_conf=global_conf)
+
+
+def _loadconfig(object_type, uri, path, name, relative_to,
+ global_conf):
+ isabs = os.path.isabs(path)
+ # De-Windowsify the paths:
+ path = path.replace('\\', '/')
+ if not isabs:
+ if not relative_to:
+ raise ValueError(
+ "Cannot resolve relative uri %r; no relative_to keyword "
+ "argument given" % uri)
+ relative_to = relative_to.replace('\\', '/')
+ if relative_to.endswith('/'):
+ path = relative_to + path
+ else:
+ path = relative_to + '/' + path
+ if path.startswith('///'):
+ path = path[2:]
+ path = unquote(path)
+ loader = ConfigLoader(path)
+ if global_conf:
+ loader.update_defaults(global_conf, overwrite=False)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['config'] = _loadconfig
+
+
+def _loadegg(object_type, uri, spec, name, relative_to,
+ global_conf):
+ loader = EggLoader(spec)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['egg'] = _loadegg
+
+
+def _loadfunc(object_type, uri, spec, name, relative_to,
+ global_conf):
+
+ loader = FuncLoader(spec)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['call'] = _loadfunc
+
+############################################################
+## Loaders
+############################################################
+
+
+class _Loader(object):
+
+ def get_app(self, name=None, global_conf=None):
+ return self.app_context(
+ name=name, global_conf=global_conf).create()
+
+ def get_filter(self, name=None, global_conf=None):
+ return self.filter_context(
+ name=name, global_conf=global_conf).create()
+
+ def get_server(self, name=None, global_conf=None):
+ return self.server_context(
+ name=name, global_conf=global_conf).create()
+
+ def app_context(self, name=None, global_conf=None):
+ return self.get_context(
+ APP, name=name, global_conf=global_conf)
+
+ def filter_context(self, name=None, global_conf=None):
+ return self.get_context(
+ FILTER, name=name, global_conf=global_conf)
+
+ def server_context(self, name=None, global_conf=None):
+ return self.get_context(
+ SERVER, name=name, global_conf=global_conf)
+
+ _absolute_re = re.compile(r'^[a-zA-Z]+:')
+
+ def absolute_name(self, name):
+ """
+ Returns true if the name includes a scheme
+ """
+ if name is None:
+ return False
+ return self._absolute_re.search(name)
+
+
+class ConfigLoader(_Loader):
+
+ def __init__(self, filename):
+ self.filename = filename = filename.strip()
+ defaults = {
+ 'here': os.path.dirname(os.path.abspath(filename)),
+ '__file__': os.path.abspath(filename)
+ }
+ self.parser = NicerConfigParser(filename, defaults=defaults)
+ self.parser.optionxform = str # Don't lower-case keys
+ with open(filename) as f:
+ self.parser.read_file(f)
+
+ def update_defaults(self, new_defaults, overwrite=True):
+ for key, value in iteritems(new_defaults):
+ if not overwrite and key in self.parser._defaults:
+ continue
+ self.parser._defaults[key] = value
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ if self.absolute_name(name):
+ return loadcontext(object_type, name,
+ relative_to=os.path.dirname(self.filename),
+ global_conf=global_conf)
+ section = self.find_config_section(
+ object_type, name=name)
+ if global_conf is None:
+ global_conf = {}
+ else:
+ global_conf = global_conf.copy()
+ defaults = self.parser.defaults()
+ global_conf.update(defaults)
+ local_conf = {}
+ global_additions = {}
+ get_from_globals = {}
+ for option in self.parser.options(section):
+ if option.startswith('set '):
+ name = option[4:].strip()
+ global_additions[name] = global_conf[name] = (
+ self.parser.get(section, option))
+ elif option.startswith('get '):
+ name = option[4:].strip()
+ get_from_globals[name] = self.parser.get(section, option)
+ else:
+ if option in defaults:
+ # @@: It's a global option (?), so skip it
+ continue
+ local_conf[option] = self.parser.get(section, option)
+ for local_var, glob_var in get_from_globals.items():
+ local_conf[local_var] = global_conf[glob_var]
+ if object_type in (APP, FILTER) and 'filter-with' in local_conf:
+ filter_with = local_conf.pop('filter-with')
+ else:
+ filter_with = None
+ if 'require' in local_conf:
+ for spec in local_conf['require'].split():
+ pkg_resources.require(spec)
+ del local_conf['require']
+ if section.startswith('filter-app:'):
+ context = self._filter_app_context(
+ object_type, section, name=name,
+ global_conf=global_conf, local_conf=local_conf,
+ global_additions=global_additions)
+ elif section.startswith('pipeline:'):
+ context = self._pipeline_app_context(
+ object_type, section, name=name,
+ global_conf=global_conf, local_conf=local_conf,
+ global_additions=global_additions)
+ elif 'use' in local_conf:
+ context = self._context_from_use(
+ object_type, local_conf, global_conf, global_additions,
+ section)
+ else:
+ context = self._context_from_explicit(
+ object_type, local_conf, global_conf, global_additions,
+ section)
+ if filter_with is not None:
+ filter_with_context = LoaderContext(
+ obj=None,
+ object_type=FILTER_WITH,
+ protocol=None,
+ global_conf=global_conf, local_conf=local_conf,
+ loader=self)
+ filter_with_context.filter_context = self.filter_context(
+ name=filter_with, global_conf=global_conf)
+ filter_with_context.next_context = context
+ return filter_with_context
+ return context
+
+ def _context_from_use(self, object_type, local_conf, global_conf,
+ global_additions, section):
+ use = local_conf.pop('use')
+ context = self.get_context(
+ object_type, name=use, global_conf=global_conf)
+ context.global_conf.update(global_additions)
+ context.local_conf.update(local_conf)
+ if '__file__' in global_conf:
+ # use sections shouldn't overwrite the original __file__
+ context.global_conf['__file__'] = global_conf['__file__']
+ # @@: Should loader be overwritten?
+ context.loader = self
+
+ if context.protocol is None:
+ # Determine protocol from section type
+ section_protocol = section.split(':', 1)[0]
+ if section_protocol in ('application', 'app'):
+ context.protocol = 'paste.app_factory'
+ elif section_protocol in ('composit', 'composite'):
+ context.protocol = 'paste.composit_factory'
+ else:
+ # This will work with 'server' and 'filter', otherwise it
+ # could fail but there is an error message already for
+ # bad protocols
+ context.protocol = 'paste.%s_factory' % section_protocol
+
+ return context
+
+ def _context_from_explicit(self, object_type, local_conf, global_conf,
+ global_addition, section):
+ possible = []
+ for protocol_options in object_type.egg_protocols:
+ for protocol in protocol_options:
+ if protocol in local_conf:
+ possible.append((protocol, local_conf[protocol]))
+ break
+ if len(possible) > 1:
+ raise LookupError(
+ "Multiple protocols given in section %r: %s"
+ % (section, possible))
+ if not possible:
+ raise LookupError(
+ "No loader given in section %r" % section)
+ found_protocol, found_expr = possible[0]
+ del local_conf[found_protocol]
+ value = import_string(found_expr)
+ context = LoaderContext(
+ value, object_type, found_protocol,
+ global_conf, local_conf, self)
+ return context
+
+ def _filter_app_context(self, object_type, section, name,
+ global_conf, local_conf, global_additions):
+ if 'next' not in local_conf:
+ raise LookupError(
+ "The [%s] section in %s is missing a 'next' setting"
+ % (section, self.filename))
+ next_name = local_conf.pop('next')
+ context = LoaderContext(None, FILTER_APP, None, global_conf,
+ local_conf, self)
+ context.next_context = self.get_context(
+ APP, next_name, global_conf)
+ if 'use' in local_conf:
+ context.filter_context = self._context_from_use(
+ FILTER, local_conf, global_conf, global_additions,
+ section)
+ else:
+ context.filter_context = self._context_from_explicit(
+ FILTER, local_conf, global_conf, global_additions,
+ section)
+ return context
+
+ def _pipeline_app_context(self, object_type, section, name,
+ global_conf, local_conf, global_additions):
+ if 'pipeline' not in local_conf:
+ raise LookupError(
+ "The [%s] section in %s is missing a 'pipeline' setting"
+ % (section, self.filename))
+ pipeline = local_conf.pop('pipeline').split()
+ if local_conf:
+ raise LookupError(
+ "The [%s] pipeline section in %s has extra "
+ "(disallowed) settings: %s"
+ % (', '.join(local_conf.keys())))
+ context = LoaderContext(None, PIPELINE, None, global_conf,
+ local_conf, self)
+ context.app_context = self.get_context(
+ APP, pipeline[-1], global_conf)
+ context.filter_contexts = [
+ self.get_context(FILTER, name, global_conf)
+ for name in pipeline[:-1]]
+ return context
+
+ def find_config_section(self, object_type, name=None):
+ """
+ Return the section name with the given name prefix (following the
+ same pattern as ``protocol_desc`` in ``config``. It must have the
+ given name, or for ``'main'`` an empty name is allowed. The
+ prefix must be followed by a ``:``.
+
+ Case is *not* ignored.
+ """
+ possible = []
+ for name_options in object_type.config_prefixes:
+ for name_prefix in name_options:
+ found = self._find_sections(
+ self.parser.sections(), name_prefix, name)
+ if found:
+ possible.extend(found)
+ break
+ if not possible:
+ raise LookupError(
+ "No section %r (prefixed by %s) found in config %s"
+ % (name,
+ ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
+ self.filename))
+ if len(possible) > 1:
+ raise LookupError(
+ "Ambiguous section names %r for section %r (prefixed by %s) "
+ "found in config %s"
+ % (possible, name,
+ ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
+ self.filename))
+ return possible[0]
+
+ def _find_sections(self, sections, name_prefix, name):
+ found = []
+ if name is None:
+ if name_prefix in sections:
+ found.append(name_prefix)
+ name = 'main'
+ for section in sections:
+ if section.startswith(name_prefix + ':'):
+ if section[len(name_prefix) + 1:].strip() == name:
+ found.append(section)
+ return found
+
+
+class EggLoader(_Loader):
+
+ def __init__(self, spec):
+ self.spec = spec
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ if self.absolute_name(name):
+ return loadcontext(object_type, name,
+ global_conf=global_conf)
+ entry_point, protocol, ep_name = self.find_egg_entry_point(
+ object_type, name=name)
+ return LoaderContext(
+ entry_point,
+ object_type,
+ protocol,
+ global_conf or {}, {},
+ self,
+ distribution=pkg_resources.get_distribution(self.spec),
+ entry_point_name=ep_name)
+
+ def find_egg_entry_point(self, object_type, name=None):
+ """
+ Returns the (entry_point, protocol) for the with the given
+ ``name``.
+ """
+ if name is None:
+ name = 'main'
+ possible = []
+ for protocol_options in object_type.egg_protocols:
+ for protocol in protocol_options:
+ pkg_resources.require(self.spec)
+ entry = pkg_resources.get_entry_info(
+ self.spec,
+ protocol,
+ name)
+ if entry is not None:
+ possible.append((entry.load(), protocol, entry.name))
+ break
+ if not possible:
+ # Better exception
+ dist = pkg_resources.get_distribution(self.spec)
+ raise LookupError(
+ "Entry point %r not found in egg %r (dir: %s; protocols: %s; "
+ "entry_points: %s)"
+ % (name, self.spec,
+ dist.location,
+ ', '.join(_flatten(object_type.egg_protocols)),
+ ', '.join(_flatten([
+ dictkeys(pkg_resources.get_entry_info(self.spec, prot, name) or {})
+ for prot in protocol_options] or '(no entry points)'))))
+ if len(possible) > 1:
+ raise LookupError(
+ "Ambiguous entry points for %r in egg %r (protocols: %s)"
+ % (name, self.spec, ', '.join(_flatten(protocol_options))))
+ return possible[0]
+
+
+class FuncLoader(_Loader):
+ """ Loader that supports specifying functions inside modules, without
+ using eggs at all. Configuration should be in the format:
+ use = call:my.module.path:function_name
+
+ Dot notation is supported in both the module and function name, e.g.:
+ use = call:my.module.path:object.method
+ """
+ def __init__(self, spec):
+ self.spec = spec
+ if not ':' in spec:
+ raise LookupError("Configuration not in format module:function")
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ obj = lookup_object(self.spec)
+ return LoaderContext(
+ obj,
+ object_type,
+ None, # determine protocol from section type
+ global_conf or {},
+ {},
+ self,
+ )
+
+
+class LoaderContext(object):
+
+ def __init__(self, obj, object_type, protocol,
+ global_conf, local_conf, loader,
+ distribution=None, entry_point_name=None):
+ self.object = obj
+ self.object_type = object_type
+ self.protocol = protocol
+ #assert protocol in _flatten(object_type.egg_protocols), (
+ # "Bad protocol %r; should be one of %s"
+ # % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols)))))
+ self.global_conf = global_conf
+ self.local_conf = local_conf
+ self.loader = loader
+ self.distribution = distribution
+ self.entry_point_name = entry_point_name
+
+ def create(self):
+ return self.object_type.invoke(self)
+
+ def config(self):
+ conf = AttrDict(self.global_conf)
+ conf.update(self.local_conf)
+ conf.local_conf = self.local_conf
+ conf.global_conf = self.global_conf
+ conf.context = self
+ return conf
+
+
+class AttrDict(dict):
+ """
+ A dictionary that can be assigned to.
+ """
+ pass
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/pastescript/serve.py
--- /dev/null
+++ b/lib/galaxy/util/pastescript/serve.py
@@ -0,0 +1,1066 @@
+# Most of this code is:
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# The server command includes the additional header:
+
+# For discussion of daemonizing:
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
+# Code taken also from QP:
+# http://www.mems-exchange.org/software/qp/
+# From lib/site.py
+
+# Galaxy originally used PasteScript and PasteDeploy for application
+# loading, to maintain compatibility we've internalized some of that
+# code here, stripping out uneeded functionality.
+
+# All top level imports from each package moved here and organized
+import ConfigParser
+import atexit
+import errno
+import getpass
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+import textwrap
+import threading
+import time
+from logging.config import fileConfig
+
+from loadwsgi import loadapp, loadserver
+
+
+difflib = None
+
+# ---- from paste.script.bool_optparse --------------------------------
+
+"""
+A subclass of ``optparse.OptionParser`` that allows boolean long
+options (like ``--verbose``) to also take arguments (like
+``--verbose=true``). Arguments *must* use ``=``.
+"""
+
+try:
+ _ = optparse._
+except AttributeError:
+ from gettext import gettext as _
+
+class BoolOptionParser(optparse.OptionParser):
+
+ def _process_long_opt(self, rargs, values):
+ arg = rargs.pop(0)
+
+ # Value explicitly attached to arg? Pretend it's the next
+ # argument.
+ if "=" in arg:
+ (opt, next_arg) = arg.split("=", 1)
+ rargs.insert(0, next_arg)
+ had_explicit_value = True
+ else:
+ opt = arg
+ had_explicit_value = False
+
+ opt = self._match_long_opt(opt)
+ option = self._long_opt[opt]
+ if option.takes_value():
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error(_("%s option requires an argument") % opt)
+ else:
+ self.error(_("%s option requires %d arguments")
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ elif had_explicit_value:
+ value = rargs[0].lower().strip()
+ del rargs[0:1]
+ if value in ('true', 'yes', 'on', '1', 'y', 't'):
+ value = None
+ elif value in ('false', 'no', 'off', '0', 'n', 'f'):
+ # Don't process
+ return
+ else:
+ self.error(_('%s option takes a boolean value only (true/false)') % opt)
+
+ else:
+ value = None
+
+ option.process(opt, value, values, self)
+
+# ---- from paste.script.command --------------------------------------
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# if sys.version_info >= (2, 6):
+# from logging.config import fileConfig
+# else:
+# # Use our custom fileConfig -- 2.5.1's with a custom Formatter class
+# # and less strict whitespace (which were incorporated into 2.6's)
+# from paste.script.util.logging_config import fileConfig
+
+class BadCommand(Exception):
+
+ def __init__(self, message, exit_code=2):
+ self.message = message
+ self.exit_code = exit_code
+ Exception.__init__(self, message)
+
+ def _get_message(self):
+ """Getter for 'message'; needed only to override deprecation
+ in BaseException."""
+ return self.__message
+
+ def _set_message(self, value):
+ """Setter for 'message'; needed only to override deprecation
+ in BaseException."""
+ self.__message = value
+
+ # BaseException.message has been deprecated since Python 2.6.
+ # To prevent DeprecationWarning from popping up over this
+ # pre-existing attribute, use a new property that takes lookup
+ # precedence.
+ message = property(_get_message, _set_message)
+
+class NoDefault(object):
+ pass
+
+# run and invoke methods moved below ServeCommand
+
+class Command(object):
+
+ def __init__(self, name):
+ self.command_name = name
+
+ max_args = None
+ max_args_error = 'You must provide no more than %(max_args)s arguments'
+ min_args = None
+ min_args_error = 'You must provide at least %(min_args)s arguments'
+ required_args = None
+ # If this command takes a configuration file, set this to 1 or -1
+ # Then if invoked through #! the config file will be put into the positional
+ # arguments -- at the beginning with 1, at the end with -1
+ takes_config_file = None
+
+ # Grouped in help messages by this:
+ group_name = ''
+
+ required_args = ()
+ description = None
+ usage = ''
+ hidden = False
+ # This is the default verbosity level; --quiet subtracts,
+ # --verbose adds:
+ default_verbosity = 0
+ # This is the default interactive state:
+ default_interactive = 0
+ return_code = 0
+
+ BadCommand = BadCommand
+
+ # Must define:
+ # parser
+ # summary
+ # command()
+
+ def run(self, args):
+ self.parse_args(args)
+
+ # Setup defaults:
+ for name, default in [('verbose', 0),
+ ('quiet', 0),
+ ('interactive', False),
+ ('overwrite', False)]:
+ if not hasattr(self.options, name):
+ setattr(self.options, name, default)
+ if getattr(self.options, 'simulate', False):
+ self.options.verbose = max(self.options.verbose, 1)
+ self.interactive = self.default_interactive
+ if getattr(self.options, 'interactive', False):
+ self.interactive += self.options.interactive
+ if getattr(self.options, 'no_interactive', False):
+ self.interactive = False
+ self.verbose = self.default_verbosity
+ self.verbose += self.options.verbose
+ self.verbose -= self.options.quiet
+ self.simulate = getattr(self.options, 'simulate', False)
+
+ # For #! situations:
+ if (os.environ.get('PASTE_CONFIG_FILE')
+ and self.takes_config_file is not None):
+ take = self.takes_config_file
+ filename = os.environ.get('PASTE_CONFIG_FILE')
+ if take == 1:
+ self.args.insert(0, filename)
+ elif take == -1:
+ self.args.append(filename)
+ else:
+ assert 0, (
+ "Value takes_config_file must be None, 1, or -1 (not %r)"
+ % take)
+
+ if (os.environ.get('PASTE_DEFAULT_QUIET')):
+ self.verbose = 0
+
+ # Validate:
+ if self.min_args is not None and len(self.args) < self.min_args:
+ raise BadCommand(
+ self.min_args_error % {'min_args': self.min_args,
+ 'actual_args': len(self.args)})
+ if self.max_args is not None and len(self.args) > self.max_args:
+ raise BadCommand(
+ self.max_args_error % {'max_args': self.max_args,
+ 'actual_args': len(self.args)})
+ for var_name, option_name in self.required_args:
+ if not getattr(self.options, var_name, None):
+ raise BadCommand(
+ 'You must provide the option %s' % option_name)
+ result = self.command()
+ if result is None:
+ return self.return_code
+ else:
+ return result
+
+ def parse_args(self, args):
+ if self.usage:
+ usage = ' '+self.usage
+ else:
+ usage = ''
+ self.parser.usage = "%%prog [options]%s\n%s" % (
+ usage, self.summary)
+ self.parser.prog = self._prog_name()
+ if self.description:
+ desc = self.description
+ desc = textwrap.dedent(desc)
+ self.parser.description = desc
+ self.options, self.args = self.parser.parse_args(args)
+
+ def _prog_name(self):
+ return '%s %s' % (os.path.basename(sys.argv[0]), self.command_name)
+
+ ########################################
+ ## Utility methods
+ ########################################
+
+ def pad(self, s, length, dir='left'):
+ if len(s) >= length:
+ return s
+ if dir == 'left':
+ return s + ' '*(length-len(s))
+ else:
+ return ' '*(length-len(s)) + s
+
+ def standard_parser(cls, verbose=True,
+ interactive=False,
+ no_interactive=False,
+ simulate=False,
+ quiet=False,
+ overwrite=False):
+ """
+ Create a standard ``OptionParser`` instance.
+
+ Typically used like::
+
+ class MyCommand(Command):
+ parser = Command.standard_parser()
+
+ Subclasses may redefine ``standard_parser``, so use the
+ nearest superclass's class method.
+ """
+ parser = BoolOptionParser()
+ if verbose:
+ parser.add_option('-v', '--verbose',
+ action='count',
+ dest='verbose',
+ default=0)
+ if quiet:
+ parser.add_option('-q', '--quiet',
+ action='count',
+ dest='quiet',
+ default=0)
+ if no_interactive:
+ parser.add_option('--no-interactive',
+ action="count",
+ dest="no_interactive",
+ default=0)
+ if interactive:
+ parser.add_option('-i', '--interactive',
+ action='count',
+ dest='interactive',
+ default=0)
+ if simulate:
+ parser.add_option('-n', '--simulate',
+ action='store_true',
+ dest='simulate',
+ default=False)
+ if overwrite:
+ parser.add_option('-f', '--overwrite',
+ dest="overwrite",
+ action="store_true",
+ help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
+ return parser
+
+ standard_parser = classmethod(standard_parser)
+
+ def quote_first_command_arg(self, arg):
+ """
+ There's a bug in Windows when running an executable that's
+ located inside a path with a space in it. This method handles
+ that case, or on non-Windows systems or an executable with no
+ spaces, it just leaves well enough alone.
+ """
+ if (sys.platform != 'win32'
+ or ' ' not in arg):
+ # Problem does not apply:
+ return arg
+ try:
+ import win32api
+ except ImportError:
+ raise ValueError(
+ "The executable %r contains a space, and in order to "
+ "handle this issue you must have the win32api module "
+ "installed" % arg)
+ arg = win32api.GetShortPathName(arg)
+ return arg
+
+ def parse_vars(self, args):
+ """
+ Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
+ 'b', 'c': 'd'}``
+ """
+ result = {}
+ for arg in args:
+ if '=' not in arg:
+ raise BadCommand(
+ 'Variable assignment %r invalid (no "=")'
+ % arg)
+ name, value = arg.split('=', 1)
+ result[name] = value
+ return result
+
+
+ def logging_file_config(self, config_file):
+ """
+ Setup logging via the logging module's fileConfig function with the
+ specified ``config_file``, if applicable.
+
+ ConfigParser defaults are specified for the special ``__file__``
+ and ``here`` variables, similar to PasteDeploy config loading.
+ """
+ parser = ConfigParser.ConfigParser()
+ parser.read([config_file])
+ if parser.has_section('loggers'):
+ config_file = os.path.abspath(config_file)
+ fileConfig(config_file, dict(__file__=config_file,
+ here=os.path.dirname(config_file)))
+
+class NotFoundCommand(Command):
+
+ def run(self, args):
+ #for name, value in os.environ.items():
+ # print '%s: %s' % (name, value)
+ #print sys.argv
+ print ('Command %r not known (you may need to run setup.py egg_info)'
+ % self.command_name)
+ commands = get_commands().items()
+ commands.sort()
+ if not commands:
+ print 'No commands registered.'
+ print 'Have you installed Paste Script?'
+ print '(try running python setup.py develop)'
+ return 2
+ print 'Known commands:'
+ longest = max([len(n) for n, c in commands])
+ for name, command in commands:
+ print ' %s %s' % (self.pad(name, length=longest),
+ command.load().summary)
+ return 2
+
+
+# ---- From paste.script.serve ----------------------------------------
+
+MAXFD = 1024
+
+jython = sys.platform.startswith('java')
+
+class DaemonizeException(Exception):
+ pass
+
+
+class ServeCommand(Command):
+
+ min_args = 0
+ usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
+ takes_config_file = 1
+ summary = "Serve the described application"
+ description = """\
+ This command serves a web application that uses a paste.deploy
+ configuration file for the server and application.
+
+ If start/stop/restart is given, then --daemon is implied, and it will
+ start (normal operation), stop (--stop-daemon), or do both.
+
+ You can also include variable assignments like 'http_port=8080'
+ and then use %(http_port)s in your config files.
+ """
+
+ # used by subclasses that configure apps and servers differently
+ requires_config_file = True
+
+ parser = Command.standard_parser(quiet=True)
+ parser.add_option('-n', '--app-name',
+ dest='app_name',
+ metavar='NAME',
+ help="Load the named application (default main)")
+ parser.add_option('-s', '--server',
+ dest='server',
+ metavar='SERVER_TYPE',
+ help="Use the named server.")
+ parser.add_option('--server-name',
+ dest='server_name',
+ metavar='SECTION_NAME',
+ help="Use the named server as defined in the configuration file (default: main)")
+ if hasattr(os, 'fork'):
+ parser.add_option('--daemon',
+ dest="daemon",
+ action="store_true",
+ help="Run in daemon (background) mode")
+ parser.add_option('--pid-file',
+ dest='pid_file',
+ metavar='FILENAME',
+ help="Save PID to file (default to paster.pid if running in daemon mode)")
+ parser.add_option('--log-file',
+ dest='log_file',
+ metavar='LOG_FILE',
+ help="Save output to the given log file (redirects stdout)")
+ parser.add_option('--reload',
+ dest='reload',
+ action='store_true',
+ help="Use auto-restart file monitor")
+ parser.add_option('--reload-interval',
+ dest='reload_interval',
+ default=1,
+ help="Seconds between checking files (low number can cause significant CPU usage)")
+ parser.add_option('--monitor-restart',
+ dest='monitor_restart',
+ action='store_true',
+ help="Auto-restart server if it dies")
+ parser.add_option('--status',
+ action='store_true',
+ dest='show_status',
+ help="Show the status of the (presumably daemonized) server")
+
+
+ if hasattr(os, 'setuid'):
+ # I don't think these are available on Windows
+ parser.add_option('--user',
+ dest='set_user',
+ metavar="USERNAME",
+ help="Set the user (usually only possible when run as root)")
+ parser.add_option('--group',
+ dest='set_group',
+ metavar="GROUP",
+ help="Set the group (usually only possible when run as root)")
+
+ parser.add_option('--stop-daemon',
+ dest='stop_daemon',
+ action='store_true',
+ help='Stop a daemonized server (given a PID file, or default paster.pid file)')
+
+ if jython:
+ parser.add_option('--disable-jython-reloader',
+ action='store_true',
+ dest='disable_jython_reloader',
+ help="Disable the Jython reloader")
+
+
+ _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
+
+ default_verbosity = 1
+
+ _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
+ _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
+
+ possible_subcommands = ('start', 'stop', 'restart', 'status')
+
+ def command(self):
+ if self.options.stop_daemon:
+ return self.stop_daemon()
+
+ if not hasattr(self.options, 'set_user'):
+ # Windows case:
+ self.options.set_user = self.options.set_group = None
+ # @@: Is this the right stage to set the user at?
+ self.change_user_group(
+ self.options.set_user, self.options.set_group)
+
+ if self.requires_config_file:
+ if not self.args:
+ raise BadCommand('You must give a config file')
+ app_spec = self.args[0]
+ if (len(self.args) > 1
+ and self.args[1] in self.possible_subcommands):
+ cmd = self.args[1]
+ restvars = self.args[2:]
+ else:
+ cmd = None
+ restvars = self.args[1:]
+ else:
+ app_spec = ""
+ if (self.args
+ and self.args[0] in self.possible_subcommands):
+ cmd = self.args[0]
+ restvars = self.args[1:]
+ else:
+ cmd = None
+ restvars = self.args[:]
+
+ if (getattr(self.options, 'daemon', False)
+ and getattr(self.options, 'reload', False)):
+ raise BadCommand('The --daemon and --reload options may not be used together')
+
+ jython_monitor = False
+ if self.options.reload:
+ if jython and not self.options.disable_jython_reloader:
+ # JythonMonitor raises the special SystemRestart
+ # exception that'll cause the Jython interpreter to
+ # reload in the existing Java process (avoiding
+ # subprocess startup time)
+ try:
+ from paste.reloader import JythonMonitor
+ except ImportError:
+ pass
+ else:
+ jython_monitor = JythonMonitor(poll_interval=int(
+ self.options.reload_interval))
+ if self.requires_config_file:
+ jython_monitor.watch_file(self.args[0])
+
+ if not jython_monitor:
+ if os.environ.get(self._reloader_environ_key):
+ from paste import reloader
+ if self.verbose > 1:
+ print 'Running reloading file monitor'
+ reloader.install(int(self.options.reload_interval))
+ if self.requires_config_file:
+ reloader.watch_file(self.args[0])
+ else:
+ return self.restart_with_reloader()
+
+ if cmd not in (None, 'start', 'stop', 'restart', 'status'):
+ raise BadCommand(
+ 'Error: must give start|stop|restart (not %s)' % cmd)
+
+ if cmd == 'status' or self.options.show_status:
+ return self.show_status()
+
+ if cmd == 'restart' or cmd == 'stop':
+ result = self.stop_daemon()
+ if result:
+ if cmd == 'restart':
+ print "Could not stop daemon; aborting"
+ else:
+ print "Could not stop daemon"
+ return result
+ if cmd == 'stop':
+ return result
+ self.options.daemon = True
+
+ if cmd == 'start':
+ self.options.daemon = True
+
+ app_name = self.options.app_name
+ vars = self.parse_vars(restvars)
+ if not self._scheme_re.search(app_spec):
+ app_spec = 'config:' + app_spec
+ server_name = self.options.server_name
+ if self.options.server:
+ server_spec = 'egg:PasteScript'
+ assert server_name is None
+ server_name = self.options.server
+ else:
+ server_spec = app_spec
+ base = os.getcwd()
+
+ if getattr(self.options, 'daemon', False):
+ if not self.options.pid_file:
+ self.options.pid_file = 'paster.pid'
+ if not self.options.log_file:
+ self.options.log_file = 'paster.log'
+
+ # Ensure the log file is writeable
+ if self.options.log_file:
+ try:
+ writeable_log_file = open(self.options.log_file, 'a')
+ except IOError, ioe:
+ msg = 'Error: Unable to write to log file: %s' % ioe
+ raise BadCommand(msg)
+ writeable_log_file.close()
+
+ # Ensure the pid file is writeable
+ if self.options.pid_file:
+ try:
+ writeable_pid_file = open(self.options.pid_file, 'a')
+ except IOError, ioe:
+ msg = 'Error: Unable to write to pid file: %s' % ioe
+ raise BadCommand(msg)
+ writeable_pid_file.close()
+
+ if getattr(self.options, 'daemon', False):
+ try:
+ self.daemonize()
+ except DaemonizeException, ex:
+ if self.verbose > 0:
+ print str(ex)
+ return
+
+ if (self.options.monitor_restart
+ and not os.environ.get(self._monitor_environ_key)):
+ return self.restart_with_monitor()
+
+ if self.options.pid_file:
+ self.record_pid(self.options.pid_file)
+
+ if self.options.log_file:
+ stdout_log = LazyWriter(self.options.log_file, 'a')
+ sys.stdout = stdout_log
+ sys.stderr = stdout_log
+ logging.basicConfig(stream=stdout_log)
+
+ log_fn = app_spec
+ if log_fn.startswith('config:'):
+ log_fn = app_spec[len('config:'):]
+ elif log_fn.startswith('egg:'):
+ log_fn = None
+ if log_fn:
+ log_fn = os.path.join(base, log_fn)
+ self.logging_file_config(log_fn)
+
+ server = loadserver(server_spec, name=server_name, relative_to=base, global_conf=vars)
+
+ app = loadapp( app_spec, name=app_name, relative_to=base, global_conf=vars)
+
+ if self.verbose > 0:
+ if hasattr(os, 'getpid'):
+ msg = 'Starting server in PID %i.' % os.getpid()
+ else:
+ msg = 'Starting server.'
+ print msg
+
+ def serve():
+ try:
+ server(app)
+ except (SystemExit, KeyboardInterrupt), e:
+ if self.verbose > 1:
+ raise
+ if str(e):
+ msg = ' '+str(e)
+ else:
+ msg = ''
+ print 'Exiting%s (-v to see traceback)' % msg
+
+ if jython_monitor:
+ # JythonMonitor has to be ran from the main thread
+ threading.Thread(target=serve).start()
+ print 'Starting Jython file monitor'
+ jython_monitor.periodic_reload()
+ else:
+ serve()
+
+ def daemonize(self):
+ pid = live_pidfile(self.options.pid_file)
+ if pid:
+ raise DaemonizeException(
+ "Daemon is already running (PID: %s from PID file %s)"
+ % (pid, self.options.pid_file))
+
+ if self.verbose > 0:
+ print 'Entering daemon mode'
+ pid = os.fork()
+ if pid:
+ # The forked process also has a handle on resources, so we
+ # *don't* want proper termination of the process, we just
+ # want to exit quick (which os._exit() does)
+ os._exit(0)
+ # Make this the session leader
+ os.setsid()
+ # Fork again for good measure!
+ pid = os.fork()
+ if pid:
+ os._exit(0)
+
+ # @@: Should we set the umask and cwd now?
+
+ import resource # Resource usage information.
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if (maxfd == resource.RLIM_INFINITY):
+ maxfd = MAXFD
+ # Iterate through and close all file descriptors.
+ for fd in range(0, maxfd):
+ try:
+ os.close(fd)
+ except OSError: # ERROR, fd wasn't open to begin with (ignored)
+ pass
+
+ if (hasattr(os, "devnull")):
+ REDIRECT_TO = os.devnull
+ else:
+ REDIRECT_TO = "/dev/null"
+ os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
+ # Duplicate standard input to standard output and standard error.
+ os.dup2(0, 1) # standard output (1)
+ os.dup2(0, 2) # standard error (2)
+
+ def record_pid(self, pid_file):
+ pid = os.getpid()
+ if self.verbose > 1:
+ print 'Writing PID %s to %s' % (pid, pid_file)
+ f = open(pid_file, 'w')
+ f.write(str(pid))
+ f.close()
+ atexit.register(_remove_pid_file, pid, pid_file, self.verbose)
+
+ def stop_daemon(self):
+ pid_file = self.options.pid_file or 'paster.pid'
+ if not os.path.exists(pid_file):
+ print 'No PID file exists in %s' % pid_file
+ return 1
+ pid = read_pidfile(pid_file)
+ if not pid:
+ print "Not a valid PID file in %s" % pid_file
+ return 1
+ pid = live_pidfile(pid_file)
+ if not pid:
+ print "PID in %s is not valid (deleting)" % pid_file
+ try:
+ os.unlink(pid_file)
+ except (OSError, IOError), e:
+ print "Could not delete: %s" % e
+ return 2
+ return 1
+ for j in range(10):
+ if not live_pidfile(pid_file):
+ break
+ import signal
+ os.kill(pid, signal.SIGTERM)
+ time.sleep(1)
+ else:
+ print "failed to kill web process %s" % pid
+ return 3
+ if os.path.exists(pid_file):
+ os.unlink(pid_file)
+ return 0
+
+ def show_status(self):
+ pid_file = self.options.pid_file or 'paster.pid'
+ if not os.path.exists(pid_file):
+ print 'No PID file %s' % pid_file
+ return 1
+ pid = read_pidfile(pid_file)
+ if not pid:
+ print 'No PID in file %s' % pid_file
+ return 1
+ pid = live_pidfile(pid_file)
+ if not pid:
+ print 'PID %s in %s is not running' % (pid, pid_file)
+ return 1
+ print 'Server running in PID %s' % pid
+ return 0
+
+ def restart_with_reloader(self):
+ self.restart_with_monitor(reloader=True)
+
+ def restart_with_monitor(self, reloader=False):
+ if self.verbose > 0:
+ if reloader:
+ print 'Starting subprocess with file monitor'
+ else:
+ print 'Starting subprocess with monitor parent'
+ while 1:
+ args = [self.quote_first_command_arg(sys.executable)] + sys.argv
+ new_environ = os.environ.copy()
+ if reloader:
+ new_environ[self._reloader_environ_key] = 'true'
+ else:
+ new_environ[self._monitor_environ_key] = 'true'
+ proc = None
+ try:
+ try:
+ _turn_sigterm_into_systemexit()
+ proc = subprocess.Popen(args, env=new_environ)
+ exit_code = proc.wait()
+ proc = None
+ except KeyboardInterrupt:
+ print '^C caught in monitor process'
+ if self.verbose > 1:
+ raise
+ return 1
+ finally:
+ if (proc is not None
+ and hasattr(os, 'kill')):
+ import signal
+ try:
+ os.kill(proc.pid, signal.SIGTERM)
+ except (OSError, IOError):
+ pass
+
+ if reloader:
+ # Reloader always exits with code 3; but if we are
+ # a monitor, any exit code will restart
+ if exit_code != 3:
+ return exit_code
+ if self.verbose > 0:
+ print '-'*20, 'Restarting', '-'*20
+
+ def change_user_group(self, user, group):
+ if not user and not group:
+ return
+ import pwd, grp
+ uid = gid = None
+ if group:
+ try:
+ gid = int(group)
+ group = grp.getgrgid(gid).gr_name
+ except ValueError:
+ import grp
+ try:
+ entry = grp.getgrnam(group)
+ except KeyError:
+ raise BadCommand(
+ "Bad group: %r; no such group exists" % group)
+ gid = entry.gr_gid
+ try:
+ uid = int(user)
+ user = pwd.getpwuid(uid).pw_name
+ except ValueError:
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ raise BadCommand(
+ "Bad username: %r; no such user exists" % user)
+ if not gid:
+ gid = entry.pw_gid
+ uid = entry.pw_uid
+ if self.verbose > 0:
+ print 'Changing user to %s:%s (%s:%s)' % (
+ user, group or '(unknown)', uid, gid)
+ if hasattr(os, 'initgroups'):
+ os.initgroups(user, gid)
+ else:
+ os.setgroups([e.gr_gid for e in grp.getgrall()
+ if user in e.gr_mem] + [gid])
+ if gid:
+ os.setgid(gid)
+ if uid:
+ os.setuid(uid)
+
+class LazyWriter(object):
+
+ """
+ File-like object that opens a file lazily when it is first written
+ to.
+ """
+
+ def __init__(self, filename, mode='w'):
+ self.filename = filename
+ self.fileobj = None
+ self.lock = threading.Lock()
+ self.mode = mode
+
+ def open(self):
+ if self.fileobj is None:
+ self.lock.acquire()
+ try:
+ if self.fileobj is None:
+ self.fileobj = open(self.filename, self.mode)
+ finally:
+ self.lock.release()
+ return self.fileobj
+
+ def write(self, text):
+ fileobj = self.open()
+ fileobj.write(text)
+ fileobj.flush()
+
+ def writelines(self, text):
+ fileobj = self.open()
+ fileobj.writelines(text)
+ fileobj.flush()
+
+ def flush(self):
+ self.open().flush()
+
+def live_pidfile(pidfile):
+ """(pidfile:str) -> int | None
+ Returns an int found in the named file, if there is one,
+ and if there is a running process with that process id.
+ Return None if no such process exists.
+ """
+ pid = read_pidfile(pidfile)
+ if pid:
+ try:
+ os.kill(int(pid), 0)
+ return pid
+ except OSError, e:
+ if e.errno == errno.EPERM:
+ return pid
+ return None
+
+def read_pidfile(filename):
+ if os.path.exists(filename):
+ try:
+ f = open(filename)
+ content = f.read()
+ f.close()
+ return int(content.strip())
+ except (ValueError, IOError):
+ return None
+ else:
+ return None
+
+def _remove_pid_file(written_pid, filename, verbosity):
+ current_pid = os.getpid()
+ if written_pid != current_pid:
+ # A forked process must be exiting, not the process that
+ # wrote the PID file
+ return
+ if not os.path.exists(filename):
+ return
+ f = open(filename)
+ content = f.read().strip()
+ f.close()
+ try:
+ pid_in_file = int(content)
+ except ValueError:
+ pass
+ else:
+ if pid_in_file != current_pid:
+ print "PID file %s contains %s, not expected PID %s" % (
+ filename, pid_in_file, current_pid)
+ return
+ if verbosity > 0:
+ print "Removing PID file %s" % filename
+ try:
+ os.unlink(filename)
+ return
+ except OSError, e:
+ # Record, but don't give traceback
+ print "Cannot remove PID file: %s" % e
+ # well, at least lets not leave the invalid PID around...
+ try:
+ f = open(filename, 'w')
+ f.write('')
+ f.close()
+ except OSError, e:
+ print 'Stale PID left in file: %s (%e)' % (filename, e)
+ else:
+ print 'Stale PID removed'
+
+
+def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2):
+ """
+ This makes sure any open ports are closed.
+
+ Does this by connecting to them until they give connection
+ refused. Servers should call like::
+
+ import paste.script
+ ensure_port_cleanup([80, 443])
+ """
+ atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
+ sleeptime=sleeptime)
+
+def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2):
+ # Wait for the server to bind to the port.
+ import socket
+ import errno
+ for bound_address in bound_addresses:
+ for attempt in range(maxtries):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.connect(bound_address)
+ except socket.error, e:
+ if e.args[0] != errno.ECONNREFUSED:
+ raise
+ break
+ else:
+ time.sleep(sleeptime)
+ else:
+ raise SystemExit('Timeout waiting for port.')
+ sock.close()
+
+def _turn_sigterm_into_systemexit():
+ """
+ Attempts to turn a SIGTERM exception into a SystemExit exception.
+ """
+ try:
+ import signal
+ except ImportError:
+ return
+ def handle_term(signo, frame):
+ raise SystemExit
+ signal.signal(signal.SIGTERM, handle_term)
+
+# ---- from paste.script.command --------------------------------------
+
+python_version = sys.version.splitlines()[0].strip()
+
+parser = optparse.OptionParser(add_help_option=False,
+ # version='%s from %s (python %s)'
+ # % (dist, dist.location, python_version),
+ usage='%prog [paster_options] COMMAND [command_options]')
+
+parser.add_option(
+ '-h', '--help',
+ action='store_true',
+ dest='do_help',
+ help="Show this help message")
+parser.disable_interspersed_args()
+
+# @@: Add an option to run this in another Python interpreter
+
+commands = {
+ 'serve': ServeCommand
+}
+
+def run(args=None):
+ if (not args and
+ len(sys.argv) >= 2
+ and os.environ.get('_') and sys.argv[0] != os.environ['_']
+ and os.environ['_'] == sys.argv[1]):
+ # probably it's an exe execution
+ args = ['exe', os.environ['_']] + sys.argv[2:]
+ if args is None:
+ args = sys.argv[1:]
+ options, args = parser.parse_args(args)
+ options.base_parser = parser
+ if options.do_help:
+ args = ['help'] + args
+ if not args:
+ print 'Usage: %s COMMAND' % sys.argv[0]
+ args = ['help']
+ command_name = args[0]
+ if command_name not in commands:
+ command = NotFoundCommand
+ else:
+ command = commands[command_name]
+ invoke(command, command_name, options, args[1:])
+
+
+def invoke(command, command_name, options, args):
+ try:
+ runner = command(command_name)
+ exit_code = runner.run(args)
+ except BadCommand, e:
+ print e.message
+ exit_code = e.exit_code
+ sys.exit(exit_code)
\ No newline at end of file
diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -23,8 +23,8 @@
import helpers
-pkg_resources.require( "PasteDeploy" )
-from paste.deploy.converters import asbool
+from galaxy.util import asbool
+
import paste.httpexceptions
pkg_resources.require( "Mako" )
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
01 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0ded962ca5d0/
changeset: 0ded962ca5d0
branch: next-stable
user: natefoo
date: 2013-02-01 22:19:55
summary: Merged changes from the default branch.
affected #: 16 files
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 lib/galaxy/tool_shed/tool_dependencies/install_util.py
--- a/lib/galaxy/tool_shed/tool_dependencies/install_util.py
+++ b/lib/galaxy/tool_shed/tool_dependencies/install_util.py
@@ -227,8 +227,8 @@
tool_dependency_type='package',
tool_dependency_name=package_name,
tool_dependency_version=package_version )
- assert os.path.exists( required_repository_package_install_dir ), \
- 'Missing required tool dependency directory %s' % str( required_repository_package_install_dir )
+ if not os.path.exists( required_repository_package_install_dir ):
+ print 'Missing required tool dependency directory %s' % str( required_repository_package_install_dir )
repo_files_dir = required_repository.repo_files_directory( app )
tool_dependencies_config = get_absolute_path_to_file_in_repository( repo_files_dir, 'tool_dependencies.xml' )
if tool_dependencies_config:
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 lib/galaxy/util/shed_util_common.py
--- a/lib/galaxy/util/shed_util_common.py
+++ b/lib/galaxy/util/shed_util_common.py
@@ -935,6 +935,11 @@
for repository_components_list in val:
tool_shed, name, owner, changeset_revision = repository_components_list
repository = get_or_create_tool_shed_repository( trans, tool_shed, name, owner, changeset_revision )
+def generate_citable_link_for_repository_in_tool_shed( trans, repository ):
+ """Generate the URL for citing a repository that is in the tool shed."""
+ base_url = url_for( '/', qualified=True ).rstrip( '/' )
+ protocol, base = base_url.split( '://' )
+ return '%s://%s/view/%s/%s' % ( protocol, base, repository.user.username, repository.name )
def generate_clone_url_for_installed_repository( app, repository ):
"""Generate the URL for cloning a repository that has been installed into a Galaxy instance."""
tool_shed_url = get_url_from_repository_tool_shed( app, repository )
@@ -1215,6 +1220,7 @@
"""
repository_dependency_tup = []
requirements_dict = {}
+ error_message = ''
package_name = elem.get( 'name', None )
package_version = elem.get( 'version', None )
if package_name and package_version:
@@ -1226,15 +1232,15 @@
requirements_dict[ 'readme' ] = sub_elem.text
elif sub_elem.tag == 'repository':
# We have a complex repository dependency.
- current_rd_tups = handle_repository_elem( app=app,
- repository_elem=sub_elem,
- repository_dependencies_tups=None )
+ current_rd_tups, error_message = handle_repository_elem( app=app,
+ repository_elem=sub_elem,
+ repository_dependencies_tups=None )
if current_rd_tups:
repository_dependency_tup = current_rd_tups[ 0 ]
if requirements_dict:
dependency_key = '%s/%s' % ( package_name, package_version )
tool_dependencies_dict[ dependency_key ] = requirements_dict
- return tool_dependencies_dict, repository_dependency_tup
+ return tool_dependencies_dict, repository_dependency_tup, error_message
def generate_repository_dependency_metadata_for_installed_repository( app, repository_dependencies_config, metadata_dict ):
"""
Generate a repository dependencies dictionary based on valid information defined in the received repository_dependencies_config. This method
@@ -1284,7 +1290,10 @@
is_valid = False
if is_valid:
for repository_elem in root.findall( 'repository' ):
- current_rd_tups = handle_repository_elem( app, repository_elem, repository_dependencies_tups )
+ current_rd_tups, error_message = handle_repository_elem( app, repository_elem, repository_dependencies_tups )
+ if error_message:
+ log.debug( error_message )
+ return metadata_dict, error_message
for crdt in current_rd_tups:
repository_dependencies_tups.append( crdt )
if repository_dependencies_tups:
@@ -1315,9 +1324,12 @@
repository_dependency_tups = []
for elem in root:
if elem.tag == 'package':
- tool_dependencies_dict, repository_dependency_tup = generate_package_dependency_metadata( app, elem, tool_dependencies_dict )
+ tool_dependencies_dict, repository_dependency_tup, message = generate_package_dependency_metadata( app, elem, tool_dependencies_dict )
if repository_dependency_tup and repository_dependency_tup not in repository_dependency_tups:
repository_dependency_tups.append( repository_dependency_tup )
+ if message:
+ log.debug( message )
+ error_message = '%s %s' % ( error_message, message )
elif elem.tag == 'set_environment':
tool_dependencies_dict = generate_environment_dependency_metadata( elem, tool_dependencies_dict )
if tool_dependencies_dict:
@@ -1345,27 +1357,23 @@
err_msg += "because the changeset revision is invalid. "
log.debug( err_msg )
error_message += err_msg
- continue
else:
err_msg = "Ignoring repository dependency definition for tool shed %s, name %s, owner %s, changeset revision %s "% \
( rd_tool_shed, rd_name, rd_owner, rd_changeset_revision )
err_msg += "because the owner is invalid. "
log.debug( err_msg )
error_message += err_msg
- continue
else:
err_msg = "Ignoring repository dependency definition for tool shed %s, name %s, owner %s, changeset revision %s "% \
( rd_tool_shed, rd_name, rd_owner, rd_changeset_revision )
err_msg += "because the name is invalid. "
log.debug( err_msg )
error_message += err_msg
- continue
else:
err_msg = "Repository dependencies are currently supported only within the same tool shed. Ignoring repository dependency definition "
err_msg += "for tool shed %s, name %s, owner %s, changeset revision %s. " % ( rd_tool_shed, rd_name, rd_owner, rd_changeset_revision )
log.debug( err_msg )
error_message += err_msg
- continue
else:
repository_owner = repository.owner
rd_key = container_util.generate_repository_dependencies_key_for_repository( toolshed_base_url=rd_tool_shed,
@@ -1836,6 +1844,13 @@
elif len( repo_info_tuple ) == 7:
description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies = repo_info_tuple
return description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies
+def get_repository_by_id( app, id ):
+ """Get a repository from the database via id."""
+ sa_session = app.model.context.current
+ if app.name == 'galaxy':
+ return sa_session.query( app.model.ToolShedRepository ).get( id )
+ else:
+ return sa_session.query( app.model.Repository ).get( id )
def get_repository_by_name( app, name ):
"""Get a repository from the database via name."""
sa_session = app.model.context.current
@@ -2470,6 +2485,7 @@
new_rd_tups = []
else:
new_rd_tups = [ rdt for rdt in repository_dependencies_tups ]
+ error_message = ''
sa_session = app.model.context.current
toolshed = repository_elem.attrib[ 'toolshed' ]
name = repository_elem.attrib[ 'name' ]
@@ -2485,8 +2501,9 @@
app.model.ToolShedRepository.table.c.owner == owner ) ) \
.first()
except:
- log.debug( "Invalid name %s or owner %s defined for repository. Repository dependencies will be ignored." % ( name, owner ) )
- return new_rd_tups
+ error_message = "Invalid name <b>%s</b> or owner <b>%s</b> defined for repository. Repository dependencies will be ignored." % ( name, owner )
+ log.debug( error_message )
+ return new_rd_tups, error_message
repository_dependencies_tup = ( toolshed, name, owner, changeset_revision )
if repository_dependencies_tup not in new_rd_tups:
new_rd_tups.append( repository_dependencies_tup )
@@ -2498,25 +2515,39 @@
.filter( app.model.User.table.c.username == owner ) \
.one()
except Exception, e:
- log.debug( "Invalid owner %s defined for repository %s. Repository dependencies will be ignored." % ( owner, name ) )
- return new_rd_tups
+ error_message = "Invalid owner <b>%s</b> defined for repository <b>%s</b>. Repository dependencies will be ignored." % ( str( owner ), str( name ) )
+ log.debug( error_message )
+ return new_rd_tups, error_message
try:
repository = sa_session.query( app.model.Repository ) \
.filter( and_( app.model.Repository.table.c.name == name,
app.model.Repository.table.c.user_id == user.id ) ) \
- .first()
+ .one()
except:
- log.debug( "Invalid name %s or owner %s defined for repository. Repository dependencies will be ignored." % ( name, owner ) )
- return new_rd_tups
+ error_message = "Invalid repository name <b>%s</b> defined. Repository dependencies will be ignored." % str( name )
+ log.debug( error_message )
+ return new_rd_tups, error_message
+ # Find the specified changeset revision in the repository's changelog to see if it's valid.
+ found = False
+ repo = hg.repository( get_configured_ui(), repository.repo_path( app ) )
+ for changeset in repo.changelog:
+ changeset_hash = str( repo.changectx( changeset ) )
+ if changeset_hash == changeset_revision:
+ found = True
+ break
+ if not found:
+ error_message = "Invalid changeset revision <b>%s</b> defined. Repository dependencies will be ignored." % str( changeset_revision )
+ log.debug( error_message )
+ return new_rd_tups, error_message
repository_dependencies_tup = ( toolshed, name, owner, changeset_revision )
if repository_dependencies_tup not in new_rd_tups:
new_rd_tups.append( repository_dependencies_tup )
else:
# Repository dependencies are currentlhy supported within a single tool shed.
- error_message = "Invalid tool shed %s defined for repository %s. " % ( toolshed, name )
- error_message += "Repository dependencies are currently supported within a single tool shed, so your definition will be ignored."
+ error_message = "Invalid tool shed <b>%s</b> defined for repository <b>%s</b>. " % ( toolshed, name )
+ error_message += "Repository dependencies are currently supported within a single tool shed."
log.debug( error_message )
- return new_rd_tups
+ return new_rd_tups, error_message
def handle_sample_files_and_load_tool_from_disk( trans, repo_files_dir, tool_config_filepath, work_dir ):
# Copy all sample files from disk to a temporary directory since the sample files may be in multiple directories.
message = ''
@@ -3147,7 +3178,9 @@
resetting_all_metadata_on_repository=True,
updating_installed_repository=False,
persist=False )
- invalid_file_tups.extend( invalid_tups )
+ # We'll only display error messages for the repository tip (it may be better to display error messages for each installable changeset revision).
+ if current_metadata_dict == repository.tip( trans.app ):
+ invalid_file_tups.extend( invalid_tups )
if current_metadata_dict:
if not metadata_changeset_revision and not metadata_dict:
# We're at the first change set in the change log.
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 lib/galaxy/webapps/community/buildapp.py
--- a/lib/galaxy/webapps/community/buildapp.py
+++ b/lib/galaxy/webapps/community/buildapp.py
@@ -63,6 +63,8 @@
# Create the universe WSGI application
webapp = CommunityWebApplication( app, session_cookie='galaxycommunitysession', name="community" )
add_ui_controllers( webapp, app )
+ webapp.add_route( '/view/{owner}/', controller='repository', action='citable_owner' )
+ webapp.add_route( '/view/{owner}/{name}/', controller='repository', action='citable_repository' )
webapp.add_route( '/:controller/:action', action='index' )
webapp.add_route( '/:action', controller='repository', action='index' )
webapp.add_route( '/repos/*path_info', controller='hg', action='handle_request', path_info='/' )
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 lib/galaxy/webapps/community/controllers/repository.py
--- a/lib/galaxy/webapps/community/controllers/repository.py
+++ b/lib/galaxy/webapps/community/controllers/repository.py
@@ -893,6 +893,47 @@
url += '&latest_ctx_rev=%s' % str( update_to_ctx.rev() )
return trans.response.send_redirect( url )
@web.expose
+ def citable_owner( self, trans, owner ):
+ """Support for citeable URL for each repository owner's tools, e.g. http://example.org/view/owner."""
+ try:
+ user = suc.get_user_by_username( trans, owner )
+ except:
+ user = None
+ if user:
+ user_id = trans.security.encode_id( user.id )
+ return trans.fill_template( "/webapps/community/citable_repository.mako",
+ user_id=user_id )
+ else:
+ message = "No repositories exist with owner <b>%s</b>." % str( owner )
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_categories',
+ id=None,
+ name=None,
+ owner=None,
+ message=message,
+ status='error' ) )
+ @web.expose
+ def citable_repository( self, trans, owner, name ):
+ """Support for citeable URL for each repository, e.g. http://example.org/view/owner/name."""
+ try:
+ repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
+ except:
+ repository = None
+ if repository:
+ repository_id = trans.security.encode_id( repository.id )
+ return trans.fill_template( "/webapps/community/citable_repository.mako",
+ repository_id=repository_id )
+ else:
+ #TODO - If the owner is OK, show their repositories?
+ message = "No repositories named <b>%s</b> with owner <b>%s</b> exist." % ( str( name ), str( owner ) )
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_categories',
+ id=None,
+ name=None,
+ owner=None,
+ message=message,
+ status='error' ) )
+ @web.expose
def contact_owner( self, trans, id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -2551,6 +2592,17 @@
message=message,
status=status )
@web.expose
+ def view_citable_repositories_by_owner( self, trans, user_id, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_repositories',
+ operation="repositories_by_user",
+ user_id=user_id ) )
+ @web.expose
+ def view_citable_repository( self, trans, repository_id, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='view_repository',
+ id=repository_id ) )
+ @web.expose
def view_or_manage_repository( self, trans, **kwd ):
repository = suc.get_repository_in_tool_shed( trans, kwd[ 'id' ] )
if trans.user_is_admin() or repository.user == trans.user:
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 templates/webapps/community/citable_repository.mako
--- /dev/null
+++ b/templates/webapps/community/citable_repository.mako
@@ -0,0 +1,84 @@
+<%inherit file="/webapps/community/base_panels.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+<%def name="stylesheets()">
+ ${parent.stylesheets()}
+ ## Include "base.css" for styling tool menu and forms (details)
+ ${h.css( "base", "autocomplete_tagging", "tool_menu" )}
+
+ ## But make sure styles for the layout take precedence
+ ${parent.stylesheets()}
+
+ <style type="text/css">
+ body { margin: 0; padding: 0; overflow: hidden; }
+ #left {
+ background: #C1C9E5 url(${h.url_for('/static/style/menu_bg.png')}) top repeat-x;
+ }
+ </style>
+</%def>
+
+<%def name="javascripts()">
+ ${parent.javascripts()}
+</%def>
+
+<%def name="init()">
+ <%
+ self.has_left_panel=True
+ self.has_right_panel=False
+ self.active_view="tools"
+ %>
+ %if trans.app.config.require_login and not trans.user:
+ <script type="text/javascript">
+ if ( window != top ) {
+ top.location.href = location.href;
+ }
+ </script>
+ %endif
+</%def>
+
+<%def name="left_panel()">
+ <div class="page-container" style="padding: 10px;">
+ <div class="toolMenu">
+ <div class="toolSectionList">
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ Search
+ </div>
+ <div class="toolSectionBody">
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='find_tools' )}">Search for valid tools</a>
+ </div>
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='find_workflows' )}">Search for workflows</a>
+ </div>
+ </div>
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ All Repositories
+ </div>
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_categories' )}">Browse by category</a>
+ </div>
+ <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' )}">Login to create a repository</a>
+ </div>
+ </div>
+ </div>
+ </div>
+</%def>
+
+<%def name="center_panel()">
+ <%
+ if trans.app.config.require_login and not trans.user:
+ center_url = h.url_for( controller='user', action='login' )
+ elif repository_id:
+ center_url = h.url_for( controller='repository', action='view_citable_repository', repository_id=repository_id )
+ else:
+ center_url = h.url_for( controller='repository', action='view_citable_repositories_by_owner', user_id=user_id )
+ %>
+ <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"></iframe>
+</%def>
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 templates/webapps/community/repository/common.mako
--- a/templates/webapps/community/repository/common.mako
+++ b/templates/webapps/community/repository/common.mako
@@ -163,6 +163,14 @@
</script></%def>
+<%def name="render_sharable_str( repository )">
+ <%
+ from galaxy.util.shed_util_common import generate_citable_link_for_repository_in_tool_shed
+ citable_str = generate_citable_link_for_repository_in_tool_shed( trans, repository )
+ %>
+ ${citable_str}
+</%def>
+
<%def name="render_clone_str( repository )"><%
from galaxy.util.shed_util_common import generate_clone_url_for_repository_in_tool_shed
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 templates/webapps/community/repository/manage_repository.mako
--- a/templates/webapps/community/repository/manage_repository.mako
+++ b/templates/webapps/community/repository/manage_repository.mako
@@ -146,6 +146,10 @@
<div class="toolFormTitle">Repository '${repository.name | h}'</div><div class="toolFormBody"><form name="edit_repository" id="edit_repository" action="${h.url_for( controller='repository', action='manage_repository', id=trans.security.encode_id( repository.id ) )}" method="post" >
+ <div class="form-row">
+ <label>Sharable link to this repository:</label>
+ ${render_sharable_str( repository )}
+ </div>
%if can_download:
<div class="form-row"><label>Clone this repository:</label>
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 templates/webapps/community/repository/view_repository.mako
--- a/templates/webapps/community/repository/view_repository.mako
+++ b/templates/webapps/community/repository/view_repository.mako
@@ -139,6 +139,10 @@
<div class="toolForm"><div class="toolFormTitle">Repository '${repository.name}'</div><div class="toolFormBody">
+ <div class="form-row">
+ <label>Sharable link to this repository:</label>
+ ${render_sharable_str( repository )}
+ </div>
%if can_download:
<div class="form-row"><label>Clone this repository:</label>
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/base/common.py
--- a/test/tool_shed/base/common.py
+++ b/test/tool_shed/base/common.py
@@ -18,6 +18,14 @@
test_user_3_email = 'test-3(a)bx.psu.edu'
test_user_3_name = 'user3'
+complex_repository_dependency_template = '''<?xml version="1.0"?>
+<tool_dependency>
+ <package name="${package}" version="${version}">
+${dependency_lines}
+ </package>
+</tool_dependency>
+'''
+
new_repository_dependencies_xml = '''<?xml version="1.0"?><repositories${description}>
${dependency_lines}
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -203,6 +203,16 @@
return '%s=%s&%s=%s' % ( field_name, field_value, field_name, field_value )
else:
return '%s=%s' % ( field_name, field_value )
+ def create_repository_complex_dependency( self, repository, xml_filename, depends_on={} ):
+ self.generate_repository_dependency_xml( depends_on[ 'repositories' ],
+ xml_filename,
+ complex=True,
+ package=depends_on[ 'package' ],
+ version=depends_on[ 'version' ] )
+ self.upload_file( repository,
+ 'tool_dependencies.xml',
+ filepath=os.path.split( xml_filename )[0],
+ commit_message='Uploaded dependency on %s.' % ', '.join( repo.name for repo in depends_on[ 'repositories' ] ) )
def create_repository_dependency( self, repository=None, depends_on=[], filepath=None ):
dependency_description = '%s depends on %s.' % ( repository.name, ', '.join( repo.name for repo in depends_on ) )
self.generate_repository_dependency_xml( depends_on,
@@ -426,7 +436,25 @@
self.visit_galaxy_url( "/user/logout" )
self.check_page_for_string( "You have been logged out" )
self.home()
- def generate_repository_dependency_xml( self, repositories, xml_filename, dependency_description='' ):
+ def generate_invalid_dependency_xml( self, xml_filename, url, name, owner, changeset_revision, complex=True, package=None, version=None, description=None ):
+ file_path = os.path.split( xml_filename )[0]
+ dependency_entries = []
+ template = string.Template( common.new_repository_dependencies_line )
+ dependency_entries.append( template.safe_substitute( toolshed_url=url,
+ owner=owner,
+ repository_name=name,
+ changeset_revision=changeset_revision ) )
+ if not os.path.exists( file_path ):
+ os.makedirs( file_path )
+ if complex:
+ dependency_template = string.Template( common.complex_repository_dependency_template )
+ repository_dependency_xml = dependency_template.safe_substitute( package=package, version=version, dependency_lines='\n'.join( dependency_entries ) )
+ else:
+ template_parser = string.Template( common.new_repository_dependencies_xml )
+ repository_dependency_xml = template_parser.safe_substitute( description=description, dependency_lines='\n'.join( dependency_entries ) )
+ # Save the generated xml to the specified location.
+ file( xml_filename, 'w' ).write( repository_dependency_xml )
+ def generate_repository_dependency_xml( self, repositories, xml_filename, dependency_description='', complex=False, package=None, version=None ):
file_path = os.path.split( xml_filename )[0]
if not os.path.exists( file_path ):
os.makedirs( file_path )
@@ -442,8 +470,12 @@
description = ' description="%s"' % dependency_description
else:
description = dependency_description
- template_parser = string.Template( common.new_repository_dependencies_xml )
- repository_dependency_xml = template_parser.safe_substitute( description=description, dependency_lines='\n'.join( dependency_entries ) )
+ if complex:
+ dependency_template = string.Template( common.complex_repository_dependency_template )
+ repository_dependency_xml = dependency_template.safe_substitute( package=package, version=version, dependency_lines='\n'.join( dependency_entries ) )
+ else:
+ template_parser = string.Template( common.new_repository_dependencies_xml )
+ repository_dependency_xml = template_parser.safe_substitute( description=description, dependency_lines='\n'.join( dependency_entries ) )
# Save the generated xml to the specified location.
file( xml_filename, 'w' ).write( repository_dependency_xml )
def generate_temp_path( self, test_script_path, additional_paths=[] ):
@@ -655,6 +687,11 @@
if includes_tools:
self.submit_form( 1, 'select_tool_panel_section_button', **kwd )
self.check_for_strings( post_submit_strings_displayed, strings_not_displayed )
+ else:
+ self.check_for_strings(strings_displayed=[ 'Choose the configuration file whose tool_path setting will be used for installing repositories' ] )
+ args = dict( shed_tool_conf=self.shed_tool_conf )
+ self.submit_form( 1, 'select_shed_tool_panel_config_button', **args )
+ self.check_for_strings( post_submit_strings_displayed, strings_not_displayed )
repository_ids = self.initiate_installation_process( new_tool_panel_section=new_tool_panel_section )
self.wait_for_repository_installation( repository_ids )
def load_invalid_tool_page( self, repository, tool_xml, changeset_revision, strings_displayed=[], strings_not_displayed=[] ):
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -158,3 +158,19 @@
'''Verify that resetting the metadata does not change it.'''
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
self.verify_unchanged_repository_metadata( repository )
+ def test_0090_verify_reserved_repository_name_handling( self ):
+ '''Check that reserved repository names are handled correctly.'''
+ category = test_db_util.get_category_by_name( 'Test 0000 Basic Repository Features 1' )
+ self.get_or_create_repository( name='repos',
+ description=repository_description,
+ long_description=repository_long_description,
+ owner=common.test_user_1_name,
+ category_id=self.security.encode_id( category.id ),
+ strings_displayed=[ 'The term <b>repos</b> is a reserved word in the tool shed, so it cannot be used as a repository name.' ] )
+ def test_0100_verify_reserved_username_handling( self ):
+ '''Check that reserved usernames are handled correctly.'''
+ self.logout()
+ self.login( email='baduser(a)bx.psu.edu', username='repos' )
+ test_user_1 = test_db_util.get_user( 'baduser(a)bx.psu.edu' )
+ assert test_user_1 is None, 'Creating user with public name "repos" succeeded.'
+ self.check_for_strings( strings_displayed=[ 'The term <b>repos</b> is a reserved word in the tool shed, so it cannot be used as a public user name.' ] )
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/functional/test_0100_complex_repository_dependencies.py
--- /dev/null
+++ b/test/tool_shed/functional/test_0100_complex_repository_dependencies.py
@@ -0,0 +1,154 @@
+from tool_shed.base.twilltestcase import ShedTwillTestCase, common, os
+import tool_shed.base.test_db_util as test_db_util
+
+bwa_base_repository_name = 'bwa_base_repository_0100'
+bwa_base_repository_description = "BWA Base"
+bwa_base_repository_long_description = "BWA tool that depends on bwa 0.5.9, with a complex repository dependency pointing at bwa_tool_repository_0100"
+
+bwa_tool_repository_name = 'bwa_tool_repository_0100'
+bwa_tool_repository_description = "BWA Tool"
+bwa_tool_repository_long_description = "BWA repository with a package tool dependency defined for BWA 0.5.9."
+
+category_name = 'Test 0100 Complex Repository Dependencies'
+category_description = 'Test 0100 Complex Repository Dependencies'
+
+class TestComplexRepositoryDependencies( ShedTwillTestCase ):
+ '''Test features related to complex repository dependencies.'''
+ def test_0000_initiate_users( self ):
+ """Create necessary user accounts."""
+ self.logout()
+ self.login( email=common.test_user_1_email, username=common.test_user_1_name )
+ test_user_1 = test_db_util.get_user( common.test_user_1_email )
+ assert test_user_1 is not None, 'Problem retrieving user with email %s from the database' % test_user_1_email
+ test_user_1_private_role = test_db_util.get_private_role( test_user_1 )
+ self.logout()
+ self.login( email=common.admin_email, username=common.admin_username )
+ admin_user = test_db_util.get_user( common.admin_email )
+ assert admin_user is not None, 'Problem retrieving user with email %s from the database' % admin_email
+ admin_user_private_role = test_db_util.get_private_role( admin_user )
+ def test_0005_create_bwa_tool_repository( self ):
+ '''Create and populate bwa_tool_0100.'''
+ category = self.create_category( name=category_name, description=category_description )
+ self.logout()
+ self.login( email=common.test_user_1_email, username=common.test_user_1_name )
+ repository = self.get_or_create_repository( name=bwa_tool_repository_name,
+ description=bwa_tool_repository_description,
+ long_description=bwa_tool_repository_long_description,
+ owner=common.test_user_1_name,
+ category_id=self.security.encode_id( category.id ),
+ strings_displayed=[] )
+ self.upload_file( repository,
+ 'bwa/complex/tool_dependencies.xml',
+ strings_displayed=[],
+ commit_message='Uploaded tool_dependencies.xml.' )
+ self.display_manage_repository_page( repository, strings_displayed=[ 'Tool dependencies', 'may not be', 'in this repository' ] )
+ def test_0010_create_bwa_base_repository( self ):
+ '''Create and populate bwa_base_0100.'''
+ category = self.create_category( name=category_name, description=category_description )
+ self.logout()
+ self.login( email=common.test_user_1_email, username=common.test_user_1_name )
+ repository = self.get_or_create_repository( name=bwa_base_repository_name,
+ description=bwa_base_repository_description,
+ long_description=bwa_base_repository_long_description,
+ owner=common.test_user_1_name,
+ category_id=self.security.encode_id( category.id ),
+ strings_displayed=[] )
+ tool_repository = test_db_util.get_repository_by_name_and_owner( bwa_tool_repository_name, common.test_user_1_name )
+ self.upload_file( repository,
+ 'bwa/complex/bwa_base.tar',
+ strings_displayed=[],
+ commit_message='Uploaded bwa_base.tar with tool wrapper XML, but without tool dependency XML.' )
+ def test_0015_generate_complex_repository_dependency_invalid_shed_url( self ):
+ '''Generate and upload a complex repository definition that specifies an invalid tool shed URL.'''
+ dependency_path = self.generate_temp_path( 'test_0100', additional_paths=[ 'complex', 'shed' ] )
+ xml_filename = self.get_filename( 'tool_dependencies.xml', filepath=dependency_path )
+ repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ url = 'http://http://this is not an url!'
+ name = repository.name
+ owner = repository.user.username
+ changeset_revision = self.get_repository_tip( repository )
+ self.generate_invalid_dependency_xml( xml_filename, url, name, owner, changeset_revision, complex=True, package='bwa', version='0.5.9' )
+ strings_displayed = [ 'Invalid tool shed %s defined for repository %s' % ( url, repository.name ) ]
+ self.upload_file( repository,
+ 'tool_dependencies.xml',
+ valid_tools_only=False,
+ filepath=dependency_path,
+ commit_message='Uploaded dependency on bwa_tool_0100 with invalid url.',
+ strings_displayed=strings_displayed )
+ def test_0020_generate_complex_repository_dependency_invalid_repository_name( self ):
+ '''Generate and upload a complex repository definition that specifies an invalid repository name.'''
+ dependency_path = self.generate_temp_path( 'test_0100', additional_paths=[ 'complex', 'shed' ] )
+ xml_filename = self.get_filename( 'tool_dependencies.xml', filepath=dependency_path )
+ repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ url = self.url
+ name = 'invalid_repository!?'
+ owner = repository.user.username
+ changeset_revision = self.get_repository_tip( repository )
+ self.generate_invalid_dependency_xml( xml_filename, url, name, owner, changeset_revision, complex=True, package='bwa', version='0.5.9' )
+ strings_displayed = 'Ignoring repository dependency definition for tool shed %s, name %s, owner %s' % ( url, name, owner )
+ strings_displayed += ', changeset revision %s because the name is invalid.' % changeset_revision
+ self.upload_file( repository,
+ 'tool_dependencies.xml',
+ valid_tools_only=False,
+ filepath=dependency_path,
+ commit_message='Uploaded dependency on bwa_tool_0100 with invalid repository name.',
+ strings_displayed=[ strings_displayed ] )
+ def test_0025_generate_complex_repository_dependency_invalid_owner_name( self ):
+ '''Generate and upload a complex repository definition that specifies an invalid owner.'''
+ dependency_path = self.generate_temp_path( 'test_0100', additional_paths=[ 'complex', 'shed' ] )
+ xml_filename = self.get_filename( 'tool_dependencies.xml', filepath=dependency_path )
+ repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ url = self.url
+ name = repository.name
+ owner = 'invalid_owner!?'
+ changeset_revision = self.get_repository_tip( repository )
+ self.generate_invalid_dependency_xml( xml_filename, url, name, owner, changeset_revision, complex=True, package='bwa', version='0.5.9' )
+ strings_displayed = [ 'Invalid owner %s defined for repository %s. Repository dependencies will be ignored.' % ( owner, name ) ]
+ self.upload_file( repository,
+ 'tool_dependencies.xml',
+ valid_tools_only=False,
+ filepath=dependency_path,
+ commit_message='Uploaded dependency on bwa_tool_0100 with invalid owner.',
+ strings_displayed=strings_displayed )
+ def test_0030_generate_complex_repository_dependency_invalid_changeset_revision( self ):
+ '''Generate and upload a complex repository definition that specifies an invalid changeset revision.'''
+ dependency_path = self.generate_temp_path( 'test_0100', additional_paths=[ 'complex', 'shed' ] )
+ xml_filename = self.get_filename( 'tool_dependencies.xml', filepath=dependency_path )
+ repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ url = self.url
+ name = repository.name
+ owner = repository.user.username
+ changeset_revision = '1234abcd'
+ self.generate_invalid_dependency_xml( xml_filename, url, name, owner, changeset_revision, complex=True, package='bwa', version='0.5.9' )
+ strings_displayed = 'Ignoring repository dependency definition for tool shed %s, name %s, owner %s' % ( url, name, owner )
+ strings_displayed += ', changeset revision %s because the changeset revision is invalid.' % changeset_revision
+ self.upload_file( repository,
+ 'tool_dependencies.xml',
+ valid_tools_only=False,
+ filepath=dependency_path,
+ commit_message='Uploaded dependency on bwa_tool_0100 with invalid changeset revision.',
+ strings_displayed=[ strings_displayed ] )
+ def test_0035_generate_complex_repository_dependency( self ):
+ '''Generate and upload a tool_dependencies.xml file that specifies a repository rather than a tool.'''
+ base_repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ tool_repository = test_db_util.get_repository_by_name_and_owner( bwa_tool_repository_name, common.test_user_1_name )
+ dependency_path = self.generate_temp_path( 'test_0100', additional_paths=[ 'complex' ] )
+ self.create_repository_complex_dependency( base_repository,
+ self.get_filename( 'tool_dependencies.xml', filepath=dependency_path ),
+ depends_on=dict( package='bwa', version='0.5.9', repositories=[ tool_repository ] ) )
+ self.check_repository_dependency( base_repository, tool_repository )
+ self.display_manage_repository_page( base_repository, strings_displayed=[ 'bwa', '0.5.9', 'package' ] )
+ def test_0040_update_base_repository( self ):
+ '''Upload a new tool_dependencies.xml to the tool repository, and verify that the base repository displays the new changeset.'''
+ base_repository = test_db_util.get_repository_by_name_and_owner( bwa_base_repository_name, common.test_user_1_name )
+ tool_repository = test_db_util.get_repository_by_name_and_owner( bwa_tool_repository_name, common.test_user_1_name )
+ previous_changeset = self.get_repository_tip( tool_repository )
+ self.upload_file( tool_repository,
+ 'bwa/complex/readme/tool_dependencies.xml',
+ strings_displayed=[],
+ commit_message='Uploaded new tool_dependencies.xml.' )
+ # Verify that the dependency display has been updated as a result of the new tool_dependencies.xml file.
+ self.display_manage_repository_page( base_repository,
+ strings_displayed=[ self.get_repository_tip( tool_repository ), 'bwa', '0.5.9', 'package' ],
+ strings_not_displayed=[ previous_changeset ] )
+
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/functional_tests.py
--- a/test/tool_shed/functional_tests.py
+++ b/test/tool_shed/functional_tests.py
@@ -184,27 +184,27 @@
kwargs[ 'object_store' ] = 'distributed'
kwargs[ 'distributed_object_store_config_file' ] = 'distributed_object_store_conf.xml.sample'
- toolshedapp = ToolshedUniverseApplication( job_queue_workers = 5,
- id_secret = 'changethisinproductiontoo',
- template_path = 'templates',
- database_connection = toolshed_database_connection,
- database_engine_option_pool_size = '10',
- file_path = shed_file_path,
- new_file_path = new_repos_path,
- tool_path=tool_path,
- datatype_converters_config_file = 'datatype_converters_conf.xml.sample',
- tool_parse_help = False,
- tool_data_table_config_path = galaxy_tool_data_table_conf_file,
- shed_tool_data_table_config = shed_tool_data_table_conf_file,
- log_destination = "stdout",
- use_heartbeat = False,
- allow_user_creation = True,
- allow_user_deletion = True,
- admin_users = 'test(a)bx.psu.edu',
- global_conf = global_conf,
- running_functional_tests = True,
- hgweb_config_dir = hgweb_config_dir,
- **kwargs )
+ toolshedapp = ToolshedUniverseApplication( admin_users = 'test(a)bx.psu.edu',
+ allow_user_creation = True,
+ allow_user_deletion = True,
+ database_connection = toolshed_database_connection,
+ database_engine_option_pool_size = '10',
+ datatype_converters_config_file = 'datatype_converters_conf.xml.sample',
+ file_path = shed_file_path,
+ global_conf = global_conf,
+ hgweb_config_dir = hgweb_config_dir,
+ job_queue_workers = 5,
+ id_secret = 'changethisinproductiontoo',
+ log_destination = "stdout",
+ new_file_path = new_repos_path,
+ running_functional_tests = True,
+ shed_tool_data_table_config = shed_tool_data_table_conf_file,
+ template_path = 'templates',
+ tool_path=tool_path,
+ tool_parse_help = False,
+ tool_data_table_config_path = galaxy_tool_data_table_conf_file,
+ use_heartbeat = False,
+ **kwargs )
log.info( "Embedded Toolshed application started" )
@@ -269,32 +269,32 @@
galaxy_global_conf = { '__file__' : 'universe_wsgi.ini.sample' }
if not galaxy_database_connection.startswith( 'sqlite://' ):
kwargs[ 'database_engine_option_max_overflow' ] = '20'
- galaxyapp = GalaxyUniverseApplication( job_queue_workers = 5,
- id_secret = 'changethisinproductiontoo',
- template_path = "templates",
- database_connection = galaxy_database_connection,
- database_engine_option_pool_size = '10',
- file_path = galaxy_file_path,
- new_file_path = galaxy_tempfiles,
- tool_path = tool_path,
- tool_data_path = tool_data_path,
- shed_tool_path = galaxy_shed_tool_path,
- update_integrated_tool_panel = False,
- migrated_tools_config = galaxy_migrated_tool_conf_file,
- tool_config_file = [ galaxy_tool_conf_file, galaxy_shed_tool_conf_file ],
- tool_sheds_config_file = galaxy_tool_sheds_conf_file,
- datatype_converters_config_file = "datatype_converters_conf.xml.sample",
- tool_parse_help = False,
- tool_data_table_config_path = galaxy_tool_data_table_conf_file,
- shed_tool_data_table_config = shed_tool_data_table_conf_file,
- log_destination = "stdout",
- use_heartbeat = False,
- allow_user_creation = True,
+ galaxyapp = GalaxyUniverseApplication( allow_user_creation = True,
allow_user_deletion = True,
admin_users = 'test(a)bx.psu.edu',
allow_library_path_paste = True,
+ database_connection = galaxy_database_connection,
+ database_engine_option_pool_size = '10',
+ datatype_converters_config_file = "datatype_converters_conf.xml.sample",
+ file_path = galaxy_file_path,
global_conf = global_conf,
+ id_secret = 'changethisinproductiontoo',
+ job_queue_workers = 5,
+ log_destination = "stdout",
+ migrated_tools_config = galaxy_migrated_tool_conf_file,
+ new_file_path = galaxy_tempfiles,
running_functional_tests=True,
+ shed_tool_data_table_config = shed_tool_data_table_conf_file,
+ shed_tool_path = galaxy_shed_tool_path,
+ template_path = "templates",
+ tool_data_path = tool_data_path,
+ tool_path = tool_path,
+ tool_config_file = [ galaxy_tool_conf_file, galaxy_shed_tool_conf_file ],
+ tool_sheds_config_file = galaxy_tool_sheds_conf_file,
+ tool_parse_help = False,
+ tool_data_table_config_path = galaxy_tool_data_table_conf_file,
+ update_integrated_tool_panel = False,
+ use_heartbeat = False,
**kwargs )
log.info( "Embedded Galaxy application started" )
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/test_data/bwa/complex/bwa_base.tar
Binary file test/tool_shed/test_data/bwa/complex/bwa_base.tar has changed
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/test_data/bwa/complex/readme/tool_dependencies.xml
--- /dev/null
+++ b/test/tool_shed/test_data/bwa/complex/readme/tool_dependencies.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<tool_dependency>
+ <package name="bwa" version="0.5.9">
+ <install version="1.0">
+ <actions>
+ <action type="download_by_url">http://downloads.sourceforge.net/project/bio-bwa/bwa-0.5.9.tar.bz2</action>
+ <action type="shell_command">make</action>
+ <action type="move_file">
+ <source>bwa</source>
+ <destination>$INSTALL_DIR/bin</destination>
+ </action>
+ <action type="set_environment">
+ <environment_variable name="PATH" action="prepend_to">$INSTALL_DIR/bin</environment_variable>
+ </action>
+ </actions>
+ </install>
+ <readme>
+Compiling BWA requires zlib and libpthread to be present on your system.
+ </readme>
+ </package>
+</tool_dependency>
\ No newline at end of file
diff -r e5aa9721b16dc30ca7183682c3b2b71c941b738b -r 0ded962ca5d04f86bf61087ab0d5df7cdce41ce9 test/tool_shed/test_data/bwa/complex/tool_dependencies.xml
--- /dev/null
+++ b/test/tool_shed/test_data/bwa/complex/tool_dependencies.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<tool_dependency>
+ <package name="bwa" version="0.5.9">
+ <install version="1.0">
+ <actions>
+ <action type="download_by_url">http://downloads.sourceforge.net/project/bio-bwa/bwa-0.5.9.tar.bz2</action>
+ <action type="shell_command">make</action>
+ <action type="move_file">
+ <source>bwa</source>
+ <destination>$INSTALL_DIR/bin</destination>
+ </action>
+ <action type="set_environment">
+ <environment_variable name="PATH" action="prepend_to">$INSTALL_DIR/bin</environment_variable>
+ </action>
+ </actions>
+ </install>
+ </package>
+</tool_dependency>
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Add an enhanced version of the cirable URLs for tool shed repositories contributed by Peter Cock.
by Bitbucket 01 Feb '13
by Bitbucket 01 Feb '13
01 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e27d0dd12752/
changeset: e27d0dd12752
user: greg
date: 2013-02-01 22:13:15
summary: Add an enhanced version of the cirable URLs for tool shed repositories contributed by Peter Cock.
affected #: 7 files
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 lib/galaxy/util/shed_util_common.py
--- a/lib/galaxy/util/shed_util_common.py
+++ b/lib/galaxy/util/shed_util_common.py
@@ -935,6 +935,11 @@
for repository_components_list in val:
tool_shed, name, owner, changeset_revision = repository_components_list
repository = get_or_create_tool_shed_repository( trans, tool_shed, name, owner, changeset_revision )
+def generate_citable_link_for_repository_in_tool_shed( trans, repository ):
+ """Generate the URL for citing a repository that is in the tool shed."""
+ base_url = url_for( '/', qualified=True ).rstrip( '/' )
+ protocol, base = base_url.split( '://' )
+ return '%s://%s/view/%s/%s' % ( protocol, base, repository.user.username, repository.name )
def generate_clone_url_for_installed_repository( app, repository ):
"""Generate the URL for cloning a repository that has been installed into a Galaxy instance."""
tool_shed_url = get_url_from_repository_tool_shed( app, repository )
@@ -1839,6 +1844,13 @@
elif len( repo_info_tuple ) == 7:
description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies = repo_info_tuple
return description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies
+def get_repository_by_id( app, id ):
+ """Get a repository from the database via id."""
+ sa_session = app.model.context.current
+ if app.name == 'galaxy':
+ return sa_session.query( app.model.ToolShedRepository ).get( id )
+ else:
+ return sa_session.query( app.model.Repository ).get( id )
def get_repository_by_name( app, name ):
"""Get a repository from the database via name."""
sa_session = app.model.context.current
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 lib/galaxy/webapps/community/buildapp.py
--- a/lib/galaxy/webapps/community/buildapp.py
+++ b/lib/galaxy/webapps/community/buildapp.py
@@ -63,6 +63,8 @@
# Create the universe WSGI application
webapp = CommunityWebApplication( app, session_cookie='galaxycommunitysession', name="community" )
add_ui_controllers( webapp, app )
+ webapp.add_route( '/view/{owner}/', controller='repository', action='citable_owner' )
+ webapp.add_route( '/view/{owner}/{name}/', controller='repository', action='citable_repository' )
webapp.add_route( '/:controller/:action', action='index' )
webapp.add_route( '/:action', controller='repository', action='index' )
webapp.add_route( '/repos/*path_info', controller='hg', action='handle_request', path_info='/' )
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 lib/galaxy/webapps/community/controllers/repository.py
--- a/lib/galaxy/webapps/community/controllers/repository.py
+++ b/lib/galaxy/webapps/community/controllers/repository.py
@@ -893,6 +893,47 @@
url += '&latest_ctx_rev=%s' % str( update_to_ctx.rev() )
return trans.response.send_redirect( url )
@web.expose
+ def citable_owner( self, trans, owner ):
+ """Support for citeable URL for each repository owner's tools, e.g. http://example.org/view/owner."""
+ try:
+ user = suc.get_user_by_username( trans, owner )
+ except:
+ user = None
+ if user:
+ user_id = trans.security.encode_id( user.id )
+ return trans.fill_template( "/webapps/community/citable_repository.mako",
+ user_id=user_id )
+ else:
+ message = "No repositories exist with owner <b>%s</b>." % str( owner )
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_categories',
+ id=None,
+ name=None,
+ owner=None,
+ message=message,
+ status='error' ) )
+ @web.expose
+ def citable_repository( self, trans, owner, name ):
+ """Support for citeable URL for each repository, e.g. http://example.org/view/owner/name."""
+ try:
+ repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
+ except:
+ repository = None
+ if repository:
+ repository_id = trans.security.encode_id( repository.id )
+ return trans.fill_template( "/webapps/community/citable_repository.mako",
+ repository_id=repository_id )
+ else:
+ #TODO - If the owner is OK, show their repositories?
+ message = "No repositories named <b>%s</b> with owner <b>%s</b> exist." % ( str( name ), str( owner ) )
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_categories',
+ id=None,
+ name=None,
+ owner=None,
+ message=message,
+ status='error' ) )
+ @web.expose
def contact_owner( self, trans, id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -2551,6 +2592,17 @@
message=message,
status=status )
@web.expose
+ def view_citable_repositories_by_owner( self, trans, user_id, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='browse_repositories',
+ operation="repositories_by_user",
+ user_id=user_id ) )
+ @web.expose
+ def view_citable_repository( self, trans, repository_id, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='repository',
+ action='view_repository',
+ id=repository_id ) )
+ @web.expose
def view_or_manage_repository( self, trans, **kwd ):
repository = suc.get_repository_in_tool_shed( trans, kwd[ 'id' ] )
if trans.user_is_admin() or repository.user == trans.user:
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 templates/webapps/community/citable_repository.mako
--- /dev/null
+++ b/templates/webapps/community/citable_repository.mako
@@ -0,0 +1,84 @@
+<%inherit file="/webapps/community/base_panels.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+<%def name="stylesheets()">
+ ${parent.stylesheets()}
+ ## Include "base.css" for styling tool menu and forms (details)
+ ${h.css( "base", "autocomplete_tagging", "tool_menu" )}
+
+ ## But make sure styles for the layout take precedence
+ ${parent.stylesheets()}
+
+ <style type="text/css">
+ body { margin: 0; padding: 0; overflow: hidden; }
+ #left {
+ background: #C1C9E5 url(${h.url_for('/static/style/menu_bg.png')}) top repeat-x;
+ }
+ </style>
+</%def>
+
+<%def name="javascripts()">
+ ${parent.javascripts()}
+</%def>
+
+<%def name="init()">
+ <%
+ self.has_left_panel=True
+ self.has_right_panel=False
+ self.active_view="tools"
+ %>
+ %if trans.app.config.require_login and not trans.user:
+ <script type="text/javascript">
+ if ( window != top ) {
+ top.location.href = location.href;
+ }
+ </script>
+ %endif
+</%def>
+
+<%def name="left_panel()">
+ <div class="page-container" style="padding: 10px;">
+ <div class="toolMenu">
+ <div class="toolSectionList">
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ Search
+ </div>
+ <div class="toolSectionBody">
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='find_tools' )}">Search for valid tools</a>
+ </div>
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='find_workflows' )}">Search for workflows</a>
+ </div>
+ </div>
+ <div class="toolSectionPad"></div>
+ <div class="toolSectionTitle">
+ All Repositories
+ </div>
+ <div class="toolTitle">
+ <a target="galaxy_main" href="${h.url_for( controller='repository', action='browse_categories' )}">Browse by category</a>
+ </div>
+ <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' )}">Login to create a repository</a>
+ </div>
+ </div>
+ </div>
+ </div>
+</%def>
+
+<%def name="center_panel()">
+ <%
+ if trans.app.config.require_login and not trans.user:
+ center_url = h.url_for( controller='user', action='login' )
+ elif repository_id:
+ center_url = h.url_for( controller='repository', action='view_citable_repository', repository_id=repository_id )
+ else:
+ center_url = h.url_for( controller='repository', action='view_citable_repositories_by_owner', user_id=user_id )
+ %>
+ <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"></iframe>
+</%def>
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 templates/webapps/community/repository/common.mako
--- a/templates/webapps/community/repository/common.mako
+++ b/templates/webapps/community/repository/common.mako
@@ -163,6 +163,14 @@
</script></%def>
+<%def name="render_sharable_str( repository )">
+ <%
+ from galaxy.util.shed_util_common import generate_citable_link_for_repository_in_tool_shed
+ citable_str = generate_citable_link_for_repository_in_tool_shed( trans, repository )
+ %>
+ ${citable_str}
+</%def>
+
<%def name="render_clone_str( repository )"><%
from galaxy.util.shed_util_common import generate_clone_url_for_repository_in_tool_shed
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 templates/webapps/community/repository/manage_repository.mako
--- a/templates/webapps/community/repository/manage_repository.mako
+++ b/templates/webapps/community/repository/manage_repository.mako
@@ -146,6 +146,10 @@
<div class="toolFormTitle">Repository '${repository.name | h}'</div><div class="toolFormBody"><form name="edit_repository" id="edit_repository" action="${h.url_for( controller='repository', action='manage_repository', id=trans.security.encode_id( repository.id ) )}" method="post" >
+ <div class="form-row">
+ <label>Sharable link to this repository:</label>
+ ${render_sharable_str( repository )}
+ </div>
%if can_download:
<div class="form-row"><label>Clone this repository:</label>
diff -r 3880939e0186a26f61d4793fafaaf6ab9f45c256 -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 templates/webapps/community/repository/view_repository.mako
--- a/templates/webapps/community/repository/view_repository.mako
+++ b/templates/webapps/community/repository/view_repository.mako
@@ -139,6 +139,10 @@
<div class="toolForm"><div class="toolFormTitle">Repository '${repository.name}'</div><div class="toolFormBody">
+ <div class="form-row">
+ <label>Sharable link to this repository:</label>
+ ${render_sharable_str( repository )}
+ </div>
%if can_download:
<div class="form-row"><label>Clone this repository:</label>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7cc74b8605b2/
changeset: 7cc74b8605b2
branch: stable
user: natefoo
date: 2013-02-01 22:03:14
summary: Created stable branch.
affected #: 0 files
https://bitbucket.org/galaxy/galaxy-central/commits/e5aa9721b16d/
changeset: e5aa9721b16d
branch: next-stable
user: natefoo
date: 2013-02-01 22:06:33
summary: Created next-stable branch.
affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0