galaxy-commits
Threads by month
- ----- 2026 -----
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 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
- 15302 discussions
commit/galaxy-central: james_taylor: Support for using Sentry for error logging. If sentry_dsn is set in
by Bitbucket 02 Feb '13
by Bitbucket 02 Feb '13
02 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/708d0ccee63f/
changeset: 708d0ccee63f
user: james_taylor
date: 2013-02-02 23:56:55
summary: Support for using Sentry for error logging. If sentry_dsn is s=
et in
universe_wsgi.ini, then:
- A middleware will be installed that send thrown exceptions to sentry
- All logging messages of level WARN and above will be sent to sentry
- All pages extending base/base_panels will have javascript errors
captured and sent to sentry
affected #: 11 files
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 eggs.ini
--- a/eggs.ini
+++ b/eggs.ini
@@ -66,6 +66,7 @@
wchartype =3D 0.1
Whoosh =3D 0.3.18
; fluent_logger =3D 0.3.3
+raven =3D 3.1.8
=20
; extra version information
[tags]
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -2,7 +2,7 @@
Universe configuration builder.
"""
=20
-import sys, os, tempfile
+import sys, os, tempfile, re
import logging, logging.config
import ConfigParser
from datetime import timedelta
@@ -261,11 +261,24 @@
self.api_folders =3D string_as_bool( kwargs.get( 'api_folders', Fa=
lse ) )
# This is for testing new library browsing capabilities.
self.new_lib_browse =3D string_as_bool( kwargs.get( 'new_lib_brows=
e', False ) )
+ # Error logging with sentry
+ self.sentry_dsn =3D kwargs.get( 'sentry_dsn', None )
# Logging with fluentd
self.fluent_log =3D string_as_bool( kwargs.get( 'fluent_log', Fals=
e ) )
self.fluent_host =3D kwargs.get( 'fluent_host', 'localhost' )
self.fluent_port =3D int( kwargs.get( 'fluent_port', 24224 ) )
=20
+ @property
+ def sentry_dsn_public( self ):
+ """
+ Sentry URL with private key removed for use in client side scripts=
,=20
+ sentry server will need to be configured to accept events
+ """
+ if self.sentry_dsn:
+ return re.sub( r"^([^:/?#]+:)?//(\w+):(\w+)", r"\1//\2", self.=
sentry_dsn )
+ else:
+ return None
+
def __read_tool_job_config( self, global_conf_parser, section, key ):
try:
tool_runners_config =3D global_conf_parser.items( section )
@@ -387,8 +400,7 @@
=20
def configure_logging( config ):
"""
- Allow some basic logging configuration to be read from the cherrpy
- config.
+ Allow some basic logging configuration to be read from ini file.
"""
# PasteScript will have already configured the logger if the appropria=
te
# sections were found in the config file, so we do nothing if the
@@ -420,3 +432,10 @@
# Hook everything up
handler.setFormatter( formatter )
root.addHandler( handler )
+ # If sentry is configured, also log to it
+ if config.sentry_dsn:
+ pkg_resources.require( "raven" )
+ from raven.handlers.logging import SentryHandler
+ sentry_handler =3D SentryHandler( config.sentry_dsn )
+ sentry_handler.setLevel( logging.WARN )
+ root.addHandler( sentry_handler )
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 lib/galaxy/web/framework/base.py
--- a/lib/galaxy/web/framework/base.py
+++ b/lib/galaxy/web/framework/base.py
@@ -9,7 +9,6 @@
import sys
import tarfile
import threading
-import uuid
=20
from Cookie import SimpleCookie
=20
@@ -70,9 +69,6 @@
self.mapper.explicit =3D False
self.api_mapper =3D routes.Mapper()
self.transaction_factory =3D DefaultWebTransaction
- # Each request will have a unique id. Since we are assuming
- # a threaded model for the moment we can store that here
- self.request_id =3D threading.local()
# Set if trace logging is enabled
self.trace_logger =3D None
def add_ui_controller( self, controller_name, controller ):
@@ -124,7 +120,7 @@
and calls it.
"""
# Immediately create request_id which we will use for logging
- self.request_id =3D request_id =3D uuid.uuid1().hex
+ request_id =3D environ.get( 'request_id', 'unknown' )
if self.trace_logger:
self.trace_logger.context_set( "request_id", request_id )
self.trace( message=3D"Starting request" )
@@ -136,6 +132,8 @@
self.trace_logger.context_remove( "request_id" )
=20
def handle_request( self, environ, start_response ):
+ # Grab the request_id (should have been set by middleware)
+ request_id =3D environ.get( 'request_id', 'unknown' )
# Map url using routes
path_info =3D environ.get( 'PATH_INFO', '' )
map =3D self.mapper.match( path_info, environ )
@@ -157,6 +155,7 @@
rc.environ =3D environ
# Setup the transaction
trans =3D self.transaction_factory( environ )
+ trans.request_id =3D request_id
rc.redirect =3D trans.response.send_redirect
# Get the controller class
controller_name =3D map.pop( 'controller', None )
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -240,6 +240,14 @@
from paste import recursive
app =3D recursive.RecursiveMiddleware( app, conf )
log.debug( "Enabling 'recursive' middleware" )
+ # If sentry logging is enabled, log here before propogating up to
+ # the error middleware
+ sentry_url =3D conf.get( 'sentry_url', None )
+ if sentry_url:
+ pkg_resources.require( "raven")
+ from raven import Client
+ from raven.middleware import Sentry
+ app =3D Sentry( app, Client( sentry_url ) )
# Various debug middleware that can only be turned on if the debug
# flag is set, either because they are insecure or greatly hurt
# performance
@@ -254,12 +262,6 @@
from paste.debug import profile
app =3D profile.ProfileMiddleware( app, conf )
log.debug( "Enabling 'profile' middleware" )
- # Middleware that intercepts print statements and shows them on the
- # returned page
- if asbool( conf.get( 'use_printdebug', True ) ):
- from paste.debug import prints
- app =3D prints.PrintDebugMiddleware( app, conf )
- log.debug( "Enabling 'print debug' middleware" )
if debug and asbool( conf.get( 'use_interactive', False ) ):
# Interactive exception debugging, scary dangerous if publicly
# accessible, if not enabled we'll use the regular error printing
@@ -288,6 +290,10 @@
from galaxy.web.framework.middleware.xforwardedhost import XForwardedH=
ostMiddleware
app =3D XForwardedHostMiddleware( app )
log.debug( "Enabling 'x-forwarded-host' middleware" )
+ # Request ID middleware
+ from galaxy.web.framework.middleware.request_id import RequestIDMiddle=
ware
+ app =3D RequestIDMiddleware( app )
+ log.debug( "Enabling 'Request ID' middleware" )
return app
=20
def wrap_in_static( app, global_conf, **local_conf ):
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 static/scripts/libs/raven.js
--- /dev/null
+++ b/static/scripts/libs/raven.js
@@ -0,0 +1,438 @@
+(function(){
+
+// Raven.js
+//
+// Originally based on the Arecibo JavaScript client.
+//
+// Requires:
+// * TraceKit (included in the full and minified distribution files)
+
+'use strict';
+
+// First, check for JSON support
+// If there is no JSON, we no-op the core features of Raven
+// since JSON is required to encode the payload
+var _Raven =3D window.Raven,
+ hasJSON =3D !isUndefined(window.JSON),
+ globalServer,
+ globalUser,
+ globalKey,
+ globalProject,
+ globalOptions =3D {
+ logger: 'javascript',
+ ignoreErrors: [],
+ ignoreUrls: []
+ };
+
+var TK =3D TraceKit.noConflict();
+
+// Disable Tracekit's remote fetching by default
+TK.remoteFetching =3D false;
+
+/*
+ * The core Raven object
+ */
+var Raven =3D {
+ VERSION: '@VERSION',
+
+ /*
+ * Raven.noConflict()
+ *
+ * Allow multiple versions of Raven to be installed.
+ */
+ noConflict: function() {
+ window.Raven =3D _Raven;
+ return Raven;
+ },
+
+ /*
+ * Raven.config()
+ *
+ * Configure raven with a DSN and extra options
+ */
+ config: function(dsn, options) {
+ var uri =3D parseUri(dsn),
+ lastSlash =3D uri.path.lastIndexOf('/'),
+ path =3D uri.path.substr(1, lastSlash);
+
+ // merge in options
+ if (options) {
+ each(options, function(key, value){
+ globalOptions[key] =3D value;
+ });
+ }
+
+ // "Script error." is hard coded into browsers for errors that it =
can't read.
+ // this is the result of a script being pulled in from an external=
domain and CORS.
+ globalOptions.ignoreErrors.push('Script error.');
+
+ globalKey =3D uri.user;
+ globalProject =3D ~~uri.path.substr(lastSlash + 1);
+
+ // assemble the endpoint from the uri pieces
+ globalServer =3D uri.protocol + '://' + uri.host +
+ (uri.port ? ':' + uri.port : '') +
+ '/' + path + 'api/' + globalProject + '/store/';
+
+ if (globalOptions.fetchContext) {
+ TK.remoteFetching =3D true;
+ }
+
+ // return for chaining
+ return Raven;
+ },
+
+ /*
+ * Raven.install()
+ *
+ * Installs a global window.onerror error handler
+ * to capture and report uncaught exceptions.
+ */
+ install: function() {
+ if (!isSetup()) return;
+
+ TK.report.subscribe(handleStackInfo);
+
+ return Raven;
+ },
+
+ /*
+ * Raven.context()
+ *
+ * Wrap code within a context so Raven can capture errors
+ * reliably across domains that is executed immediately.
+ */
+ context: function(options, func, args) {
+ if (isFunction(options)) {
+ args =3D func;
+ func =3D options;
+ options =3D undefined;
+ }
+
+ Raven.wrap(options, func).apply(this, args);
+ },
+
+ /* Raven.wrap()
+ *
+ * Wrap code within a context and returns back a new function to be ex=
ecuted
+ */
+ wrap: function(options, func) {
+ // options is optional
+ if (isFunction(options)) {
+ func =3D options;
+ options =3D undefined;
+ }
+
+ return function() {
+ try {
+ func.apply(this, arguments);
+ } catch(e) {
+ Raven.captureException(e, options);
+ }
+ };
+ },
+
+ /*
+ * Raven.uninstall()
+ *
+ * Uninstalls the global error handler.
+ */
+ uninstall: function() {
+ TK.report.unsubscribe(handleStackInfo);
+
+ return Raven;
+ },
+
+ /*
+ * Raven.captureException()
+ *
+ * Manually capture an exception and send it over to Sentry
+ */
+ captureException: function(ex, options) {
+ // TraceKit.report will re-raise any exception passed to it,
+ // which means you have to wrap it in try/catch. Instead, we
+ // can wrap it here and only re-raise if TraceKit.report
+ // raises an exception different from the one we asked to
+ // report on.
+ try {
+ TK.report(ex, options);
+ } catch(ex1) {
+ if(ex !=3D=3D ex1) {
+ throw ex1;
+ }
+ }
+
+ return Raven;
+ },
+
+ /*
+ * Raven.captureMessage()
+ *
+ * Manually send a message to Sentry
+ */
+ captureMessage: function(msg, options) {
+ // Fire away!
+ send(
+ arrayMerge({
+ message: msg
+ }, options)
+ );
+
+ return Raven;
+ },
+
+ /*
+ * Raven.setUser()
+ *
+ * Set/clear a user to be sent along with the payload.
+ */
+ setUser: function(user) {
+ globalUser =3D user;
+
+ return Raven;
+ }
+};
+
+var uriKeys =3D 'source protocol authority userInfo user password host por=
t relative path directory file query anchor'.split(' '),
+ uriPattern =3D /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:((=
[^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.=
[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+/**** Private functions ****/
+function parseUri(str) {
+ var m =3D uriPattern.exec(str),
+ uri =3D {},
+ i =3D 14;
+
+ while (i--) uri[uriKeys[i]] =3D m[i] || '';
+
+ return uri;
+}
+
+function isUndefined(what) {
+ return typeof what =3D=3D=3D 'undefined';
+}
+
+function isFunction(what) {
+ return typeof what =3D=3D=3D 'function';
+}
+
+function each(obj, callback) {
+ var i, j;
+
+ if (obj.length =3D=3D=3D undefined) {
+ for (i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ callback.call(null, i, obj[i]);
+ }
+ }
+ } else {
+ for (i =3D 0, j =3D obj.length; i < j; i++) {
+ callback.call(null, i, obj[i]);
+ }
+ }
+}
+
+var cachedAuth;
+
+function getAuthQueryString() {
+ if (cachedAuth) return cachedAuth;
+
+ var qs =3D [
+ 'sentry_version=3D2.0',
+ 'sentry_client=3Draven-js/' + Raven.VERSION
+ ];
+ if (globalKey) {
+ qs.push('sentry_key=3D' + globalKey);
+ }
+
+ cachedAuth =3D '?' + qs.join('&');
+ return cachedAuth;
+}
+
+function handleStackInfo(stackInfo, options) {
+ var frames =3D [], i =3D 0, j =3D stackInfo.stack && stackInfo.stack.l=
ength || 0, frame;
+
+ for (; i < j; i++) {
+ frame =3D normalizeFrame(stackInfo.stack[i]);
+ if (frame) {
+ frames.push(frame);
+ }
+ }
+
+ processException(
+ stackInfo.name,
+ stackInfo.message,
+ stackInfo.url,
+ stackInfo.lineno,
+ frames,
+ options
+ );
+}
+
+function normalizeFrame(frame) {
+ if (!frame.url) return;
+
+ // normalize the frames data
+ var normalized =3D {
+ filename: frame.url,
+ lineno: frame.line,
+ colno: frame.column,
+ 'function': frame.func || '?'
+ }, context =3D extractContextFromFrame(frame);
+
+ if (context) {
+ var i =3D 3, keys =3D ['pre_context', 'context_line', 'post_contex=
t'];
+ while (i--) normalized[keys[i]] =3D context[i];
+ }
+
+ normalized.in_app =3D !/(Raven|TraceKit)\./.test(normalized['function'=
]);
+
+ return normalized;
+}
+
+function extractContextFromFrame(frame) {
+ // immediately check if we should even attempt to parse a context
+ if (!frame.context || !globalOptions.fetchContext) return;
+
+ var context =3D frame.context,
+ pivot =3D ~~(context.length / 2),
+ i =3D context.length, isMinified =3D false;
+
+ while (i--) {
+ // We're making a guess to see if the source is minified or not.
+ // To do that, we make the assumption if *any* of the lines passed
+ // in are greater than 300 characters long, we bail.
+ // Sentry will see that there isn't a context
+ if (context[i].length > 300) {
+ isMinified =3D true;
+ break;
+ }
+ }
+
+ if (isMinified) {
+ // The source is minified and we don't know which column. Fuck it.
+ if (isUndefined(frame.column)) return;
+
+ // If the source is minified and has a frame column
+ // we take a chunk of the offending line to hopefully shed some li=
ght
+ return [
+ [], // no pre_context
+ context[pivot].substr(frame.column, 50), // grab 50 characters=
, starting at the offending column
+ [] // no post_context
+ ];
+ }
+
+ return [
+ context.slice(0, pivot), // pre_context
+ context[pivot], // context_line
+ context.slice(pivot + 1) // post_context
+ ];
+}
+
+function processException(type, message, fileurl, lineno, frames, options)=
{
+ var stacktrace, label, i;
+
+ // IE8 really doesn't have Array.prototype.indexOf
+ // Filter out a message that matches our ignore list
+ i =3D globalOptions.ignoreErrors.length;
+ while (i--) {
+ if (message =3D=3D=3D globalOptions.ignoreErrors[i]) {
+ return;
+ }
+ }
+
+ if (frames && frames.length) {
+ stacktrace =3D {frames: frames};
+ fileurl =3D fileurl || frames[0].filename;
+ } else if (fileurl) {
+ stacktrace =3D {
+ frames: [{
+ filename: fileurl,
+ lineno: lineno
+ }]
+ };
+ }
+
+ i =3D globalOptions.ignoreUrls.length;
+ while (i--) {
+ if (globalOptions.ignoreUrls[i].test(fileurl)) {
+ return;
+ }
+ }
+
+ label =3D lineno ? message + ' at ' + lineno : message;
+
+ // Fire away!
+ send(
+ arrayMerge({
+ 'sentry.interfaces.Exception': {
+ type: type,
+ value: message
+ },
+ 'sentry.interfaces.Stacktrace': stacktrace,
+ culprit: fileurl,
+ message: label
+ }, options)
+ );
+}
+
+function arrayMerge(arr1, arr2) {
+ if (!arr2) {
+ return arr1;
+ }
+ each(arr2, function(key, value){
+ arr1[key] =3D value;
+ });
+ return arr1;
+}
+
+function getHttpData() {
+ var http =3D {
+ url: window.location.href,
+ headers: {
+ 'User-Agent': navigator.userAgent
+ }
+ };
+
+ if (window.document.referrer) {
+ http.headers.Referer =3D window.document.referrer;
+ }
+
+ return http;
+}
+
+function send(data) {
+ if (!isSetup()) return;
+
+ data =3D arrayMerge({
+ project: globalProject,
+ logger: globalOptions.logger,
+ site: globalOptions.site,
+ platform: 'javascript',
+ 'sentry.interfaces.Http': getHttpData()
+ }, data );
+
+ if (globalUser) data['sentry.interfaces.User'] =3D globalUser;
+
+ if (isFunction(globalOptions.dataCallback)) {
+ data =3D globalOptions.dataCallback(data);
+ }
+
+ makeRequest(data);
+}
+
+function makeRequest(data) {
+ new Image().src =3D globalServer + getAuthQueryString() + '&sentry_dat=
a=3D' + encodeURIComponent(JSON.stringify(data));
+}
+
+function isSetup() {
+ if (!hasJSON) return false; // needs JSON support
+ if (!globalServer) {
+ console.error("Error: Raven has not been configured.");
+ return false;
+ }
+ return true;
+}
+
+window.Raven =3D Raven;
+
+})();
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 static/scripts/libs/tracekit.js
--- /dev/null
+++ b/static/scripts/libs/tracekit.js
@@ -0,0 +1,1210 @@
+/*
+ TraceKit - Cross brower stack traces - github.com/occ/TraceKit
+ MIT license
+*/
+
+;(function(window, undefined) {
+
+
+var TraceKit =3D {};
+var _oldTraceKit =3D window.TraceKit;
+
+/**
+ * TraceKit.noConflict: Export TraceKit out to another variable
+ * Example: var TK =3D TraceKit.noConflict()
+ */
+TraceKit.noConflict =3D function noConflict() {
+ window.TraceKit =3D _oldTraceKit;
+ return TraceKit;
+};
+
+/**
+ * TraceKit._has, a better form of hasOwnProperty
+ * Example: TraceKit._has(MainHostObject, property) =3D=3D=3D true/false
+ *
+ * @param {Object} host object to check property
+ * @param {string} key to check
+ */
+TraceKit._has =3D function _has(object, key) {
+ return Object.prototype.hasOwnProperty.call(object, key);
+};
+
+/**
+ * TraceKit.report: cross-browser processing of unhandled exceptions
+ *
+ * Syntax:
+ * TraceKit.report.subscribe(function(stackInfo) { ... })
+ * TraceKit.report.unsubscribe(function(stackInfo) { ... })
+ * TraceKit.report(exception)
+ * try { ...code... } catch(ex) { TraceKit.report(ex); }
+ *
+ * Supports:
+ * - Firefox: full stack trace with line numbers, plus column number
+ * on top frame; column number is not guaranteed
+ * - Opera: full stack trace with line and column numbers
+ * - Chrome: full stack trace with line and column numbers
+ * - Safari: line and column number for the top frame only; some frames
+ * may be missing, and column number is not guaranteed
+ * - IE: line and column number for the top frame only; some frames
+ * may be missing, and column number is not guaranteed
+ *
+ * In theory, TraceKit should work on all of the following versions:
+ * - IE5.5+ (only 8.0 tested)
+ * - Firefox 0.9+ (only 3.5+ tested)
+ * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
+ * Exceptions Have Stacktrace to be enabled in opera:config)
+ * - Safari 3+ (only 4+ tested)
+ * - Chrome 1+ (only 5+ tested)
+ * - Konqueror 3.5+ (untested)
+ *
+ * Requires TraceKit.computeStackTrace.
+ *
+ * Tries to catch all unhandled exceptions and report them to the
+ * subscribed handlers. Please note that TraceKit.report will rethrow the
+ * exception. This is REQUIRED in order to get a useful stack trace in IE.
+ * If the exception does not reach the top of the browser, you will only
+ * get a stack trace from the point where TraceKit.report was called.
+ *
+ * Handlers receive a stackInfo object as described in the
+ * TraceKit.computeStackTrace docs.
+ */
+TraceKit.report =3D (function reportModuleWrapper() {
+ var handlers =3D [],
+ lastException =3D null,
+ lastExceptionStack =3D null;
+
+ /**
+ * Add a crash handler.
+ * @param {Function} handler
+ */
+ function subscribe(handler) {
+ handlers.push(handler);
+ }
+
+ /**
+ * Remove a crash handler.
+ * @param {Function} handler
+ */
+ function unsubscribe(handler) {
+ for (var i =3D handlers.length - 1; i >=3D 0; --i) {
+ if (handlers[i] =3D=3D=3D handler) {
+ handlers.splice(i, 1);
+ }
+ }
+ }
+
+ /**
+ * Dispatch stack information to all handlers.
+ * @param {Object.<string, *>} stack
+ */
+ function notifyHandlers(stack, windowError) {
+ var exception =3D null;
+ if (windowError && !TraceKit.collectWindowErrors) {
+ return;
+ }
+ for (var i in handlers) {
+ if (TraceKit._has(handlers, i)) {
+ try {
+ handlers[i].apply(null, [stack].concat(Array.prototype=
.slice.call(arguments, 2)));
+ } catch (inner) {
+ exception =3D inner;
+ }
+ }
+ }
+
+ if (exception) {
+ throw exception;
+ }
+ }
+
+ var _oldOnerrorHandler =3D window.onerror;
+
+ /**
+ * Ensures all global unhandled exceptions are recorded.
+ * Supported by Gecko and IE.
+ * @param {string} message Error message.
+ * @param {string} url URL of script that generated the exception.
+ * @param {(number|string)} lineNo The line number at which the error
+ * occurred.
+ */
+ window.onerror =3D function traceKitWindowOnError(message, url, lineNo=
) {
+ var stack =3D null;
+
+ if (lastExceptionStack) {
+ TraceKit.computeStackTrace.augmentStackTraceWithInitialElement=
(lastExceptionStack, url, lineNo, message);
+ stack =3D lastExceptionStack;
+ lastExceptionStack =3D null;
+ lastException =3D null;
+ } else {
+ var location =3D {
+ 'url': url,
+ 'line': lineNo
+ };
+ location.func =3D TraceKit.computeStackTrace.guessFunctionName=
(location.url, location.line);
+ location.context =3D TraceKit.computeStackTrace.gatherContext(=
location.url, location.line);
+ stack =3D {
+ 'mode': 'onerror',
+ 'message': message,
+ 'url': document.location.href,
+ 'stack': [location],
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ notifyHandlers(stack, 'from window.onerror');
+
+ if (_oldOnerrorHandler) {
+ return _oldOnerrorHandler.apply(this, arguments);
+ }
+
+ return false;
+ };
+
+ /**
+ * Reports an unhandled Error to TraceKit.
+ * @param {Error} ex
+ */
+ function report(ex) {
+ var args =3D Array.prototype.slice.call(arguments, 1);
+ if (lastExceptionStack) {
+ if (lastException =3D=3D=3D ex) {
+ return; // already caught by an inner catch block, ignore
+ } else {
+ var s =3D lastExceptionStack;
+ lastExceptionStack =3D null;
+ lastException =3D null;
+ notifyHandlers.apply(null, [s, null].concat(args));
+ }
+ }
+
+ var stack =3D TraceKit.computeStackTrace(ex);
+ lastExceptionStack =3D stack;
+ lastException =3D ex;
+
+ // If the stack trace is incomplete, wait for 2 seconds for
+ // slow slow IE to see if onerror occurs or not before reporting
+ // this exception; otherwise, we will end up with an incomplete
+ // stack trace
+ window.setTimeout(function () {
+ if (lastException =3D=3D=3D ex) {
+ lastExceptionStack =3D null;
+ lastException =3D null;
+ notifyHandlers.apply(null, [stack, null].concat(args));
+ }
+ }, (stack.incomplete ? 2000 : 0));
+
+ throw ex; // re-throw to propagate to the top level (and cause win=
dow.onerror)
+ }
+
+ report.subscribe =3D subscribe;
+ report.unsubscribe =3D unsubscribe;
+ return report;
+}());
+
+/**
+ * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
+ *
+ * Syntax:
+ * s =3D TraceKit.computeStackTrace.ofCaller([depth])
+ * s =3D TraceKit.computeStackTrace(exception) // consider using TraceKi=
t.report instead (see below)
+ * Returns:
+ * s.name - exception name
+ * s.message - exception message
+ * s.stack[i].url - JavaScript or HTML file URL
+ * s.stack[i].func - function name, or empty for anonymous functions=
(if guessing did not work)
+ * s.stack[i].args - arguments passed to the function, if known
+ * s.stack[i].line - line number, if known
+ * s.stack[i].column - column number, if known
+ * s.stack[i].context - an array of source code lines; the middle eleme=
nt corresponds to the correct line#
+ * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', =
'onerror', or 'failed' -- method used to collect the stack trace
+ *
+ * Supports:
+ * - Firefox: full stack trace with line numbers and unreliable column
+ * number on top frame
+ * - Opera 10: full stack trace with line and column numbers
+ * - Opera 9-: full stack trace with line numbers
+ * - Chrome: full stack trace with line and column numbers
+ * - Safari: line and column number for the topmost stacktrace element
+ * only
+ * - IE: no line numbers whatsoever
+ *
+ * Tries to guess names of anonymous functions by looking for assignments
+ * in the source code. In IE and Safari, we have to guess source file names
+ * by searching for function bodies inside all page scripts. This will not
+ * work for scripts that are loaded cross-domain.
+ * Here be dragons: some function names may be guessed incorrectly, and
+ * duplicate functions may be mismatched.
+ *
+ * TraceKit.computeStackTrace should only be used for tracing purposes.
+ * Logging of unhandled exceptions should be done with TraceKit.report,
+ * which builds on top of TraceKit.computeStackTrace and provides better
+ * IE support by utilizing the window.onerror event to retrieve information
+ * about the top of the stack.
+ *
+ * Note: In IE and Safari, no stack trace is recorded on the Error object,
+ * so computeStackTrace instead walks its *own* chain of callers.
+ * This means that:
+ * * in Safari, some methods may be missing from the stack trace;
+ * * in IE, the topmost function in the stack trace will always be the
+ * caller of computeStackTrace.
+ *
+ * This is okay for tracing (because you are likely to be calling
+ * computeStackTrace from the function you want to be the topmost element
+ * of the stack trace anyway), but not okay for logging unhandled
+ * exceptions (because your catch block will likely be far away from the
+ * inner function that actually caused the exception).
+ *
+ * Tracing example:
+ * function trace(message) {
+ * var stackInfo =3D TraceKit.computeStackTrace.ofCaller();
+ * var data =3D message + "\n";
+ * for(var i in stackInfo.stack) {
+ * var item =3D stackInfo.stack[i];
+ * data +=3D (item.func || '[anonymous]') + "() in " + item.ur=
l + ":" + (item.line || '0') + "\n";
+ * }
+ * if (window.console)
+ * console.info(data);
+ * else
+ * alert(data);
+ * }
+ */
+TraceKit.computeStackTrace =3D (function computeStackTraceWrapper() {
+ var debug =3D false,
+ sourceCache =3D {};
+
+ /**
+ * Attempts to retrieve source code via XMLHttpRequest, which is used
+ * to look up anonymous function names.
+ * @param {string} url URL of source code.
+ * @return {string} Source contents.
+ */
+ function loadSource(url) {
+ if (!TraceKit.remoteFetching) { //Only attempt request if remoteFe=
tching is on.
+ return '';
+ }
+ try {
+ var XMLHttpRequestWrapper;
+
+ if (typeof (XMLHttpRequest) =3D=3D=3D 'undefined') { // IE 5.x=
-6.x:
+ XMLHttpRequestWrapper =3D function IEXMLHttpRequestSub() {
+ try {
+ return new ActiveXObject('Msxml2.XMLHTTP.6.0');
+ } catch (e) {}
+ try {
+ return new ActiveXObject('Msxml2.XMLHTTP.3.0');
+ } catch (e) {}
+ try {
+ return new ActiveXObject('Msxml2.XMLHTTP');
+ } catch (e) {}
+ try {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ } catch (e) {}
+ throw new Error('No XHR.');
+ };
+ } else {
+ XMLHttpRequestWrapper =3D XMLHttpRequest;
+ }
+ =20
+ var request =3D new XMLHttpRequestWrapper();
+ request.open('GET', url, false);
+ request.send('');
+ return request.responseText;
+ } catch (e) {
+ return '';
+ }
+ }
+
+ /**
+ * Retrieves source code from the source code cache.
+ * @param {string} url URL of source code.
+ * @return {Array.<string>} Source contents.
+ */
+ function getSource(url) {
+ if (!TraceKit._has(sourceCache, url)) {
+ // URL needs to be able to fetched within the acceptable domai=
n. Otherwise,
+ // cross-domain errors will be triggered.
+ var source;
+ if (url.indexOf(document.domain) !=3D=3D -1) {
+ source =3D loadSource(url);
+ } else {
+ source =3D [];
+ }
+ sourceCache[url] =3D source.length ? source.split('\n') : [];
+ }
+
+ return sourceCache[url];
+ }
+
+ /**
+ * Tries to use an externally loaded copy of source code to determine
+ * the name of a function by looking at the name of the variable it was
+ * assigned to, if any.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} lineNo Line number in source code.
+ * @return {string} The function name, if discoverable.
+ */
+ function guessFunctionName(url, lineNo) {
+ var reFunctionArgNames =3D /function ([^(]*)\(([^)]*)\)/,
+ reGuessFunction =3D /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=3D]\s*(fu=
nction|eval|new Function)/,
+ line =3D '',
+ maxLines =3D 10,
+ source =3D getSource(url),
+ m;
+
+ if (!source.length) {
+ return '?';
+ }
+
+ // Walk backwards from the first line in the function until we fin=
d the line which
+ // matches the pattern above, which is the function definition
+ for (var i =3D 0; i < maxLines; ++i) {
+ line =3D source[lineNo - i] + line;
+
+ if (line !=3D=3D undefined) {
+ if ((m =3D reGuessFunction.exec(line))) {
+ return m[1];
+ } else if ((m =3D reFunctionArgNames.exec(line))) {
+ return m[1];
+ }
+ }
+ }
+
+ return '?';
+ }
+
+ /**
+ * Retrieves the surrounding lines from where an exception occurred.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} line Line number in source code to centre
+ * around for context.
+ * @return {?Array.<string>} Lines of source code.
+ */
+ function gatherContext(url, line) {
+ var source =3D getSource(url);
+
+ if (!source.length) {
+ return null;
+ }
+
+ var context =3D [],
+ // linesBefore & linesAfter are inclusive with the offending l=
ine.
+ // if linesOfContext is even, there will be one extra line
+ // *before* the offending line.
+ linesBefore =3D Math.floor(TraceKit.linesOfContext / 2),
+ // Add one extra line if linesOfContext is odd
+ linesAfter =3D linesBefore + (TraceKit.linesOfContext % 2),
+ start =3D Math.max(0, line - linesBefore - 1),
+ end =3D Math.min(source.length, line + linesAfter - 1);
+
+ line -=3D 1; // convert to 0-based index
+
+ for (var i =3D start; i < end; ++i) {
+ if (typeof (source[i]) !=3D=3D 'undefined') {
+ context.push(source[i]);
+ }
+ }
+
+ return context.length > 0 ? context : null;
+ }
+
+ /**
+ * Escapes special characters, except for whitespace, in a string to be
+ * used inside a regular expression as a string literal.
+ * @param {string} text The string.
+ * @return {string} The escaped string literal.
+ */
+ function escapeRegExp(text) {
+ return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
+ }
+
+ /**
+ * Escapes special characters in a string to be used inside a regular
+ * expression as a string literal. Also ensures that HTML entities will
+ * be matched the same as their literal friends.
+ * @param {string} body The string.
+ * @return {string} The escaped string.
+ */
+ function escapeCodeAsRegExpForMatchingInsideHTML(body) {
+ return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', =
'(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').repl=
ace(/\s+/g, '\\s+');
+ }
+
+ /**
+ * Determines where a code fragment occurs in the source code.
+ * @param {RegExp} re The function definition.
+ * @param {Array.<string>} urls A list of URLs to search.
+ * @return {?Object.<string, (string|number)>} An object containing
+ * the url, line, and column number of the defined function.
+ */
+ function findSourceInUrls(re, urls) {
+ var source, m;
+ for (var i =3D 0, j =3D urls.length; i < j; ++i) {
+ // console.log('searching', urls[i]);
+ if ((source =3D getSource(urls[i])).length) {
+ source =3D source.join('\n');
+ if ((m =3D re.exec(source))) {
+ // console.log('Found function in ' + urls[i]);
+
+ return {
+ 'url': urls[i],
+ 'line': source.substring(0, m.index).split('\n').l=
ength,
+ 'column': m.index - source.lastIndexOf('\n', m.ind=
ex) - 1
+ };
+ }
+ }
+ }
+
+ // console.log('no match');
+
+ return null;
+ }
+
+ /**
+ * Determines at which column a code fragment occurs on a line of the
+ * source code.
+ * @param {string} fragment The code fragment.
+ * @param {string} url The URL to search.
+ * @param {(string|number)} line The line number to examine.
+ * @return {?number} The column number.
+ */
+ function findSourceInLine(fragment, url, line) {
+ var source =3D getSource(url),
+ re =3D new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
+ m;
+
+ line -=3D 1;
+
+ if (source && source.length > line && (m =3D re.exec(source[line])=
)) {
+ return m.index;
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines where a function was defined within the source code.
+ * @param {(Function|string)} func A function reference or serialized
+ * function definition.
+ * @return {?Object.<string, (string|number)>} An object containing
+ * the url, line, and column number of the defined function.
+ */
+ function findSourceByFunctionBody(func) {
+ var urls =3D [window.location.href],
+ scripts =3D document.getElementsByTagName('script'),
+ body,
+ code =3D '' + func,
+ codeRE =3D /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*=
(\S[\s\S]*\S)\s*\}\s*$/,
+ eventRE =3D /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]=
*\S)\s*\}\s*$/,
+ re,
+ parts,
+ result;
+
+ for (var i =3D 0; i < scripts.length; ++i) {
+ var script =3D scripts[i];
+ if (script.src) {
+ urls.push(script.src);
+ }
+ }
+
+ if (!(parts =3D codeRE.exec(code))) {
+ re =3D new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
+ }
+
+ // not sure if this is really necessary, but I don=E2=80=99t have =
a test
+ // corpus large enough to confirm that and it was in the original.
+ else {
+ var name =3D parts[1] ? '\\s+' + parts[1] : '',
+ args =3D parts[2].split(',').join('\\s*,\\s*');
+
+ body =3D escapeRegExp(parts[3]).replace(/;$/, ';?'); // semico=
lon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
+ re =3D new RegExp('function' + name + '\\s*\\(\\s*' + args + '=
\\s*\\)\\s*{\\s*' + body + '\\s*}');
+ }
+
+ // look for a normal function definition
+ if ((result =3D findSourceInUrls(re, urls))) {
+ return result;
+ }
+
+ // look for an old-school event handler function
+ if ((parts =3D eventRE.exec(code))) {
+ var event =3D parts[1];
+ body =3D escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
+
+ // look for a function defined in HTML as an onXXX handler
+ re =3D new RegExp('on' + event + '=3D[\\\'"]\\s*' + body + '\\=
s*[\\\'"]', 'i');
+
+ if ((result =3D findSourceInUrls(re, urls[0]))) {
+ return result;
+ }
+
+ // look for ???
+ re =3D new RegExp(body);
+
+ if ((result =3D findSourceInUrls(re, urls))) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ // Contents of Exception in various browsers.
+ //
+ // SAFARI:
+ // ex.message =3D Can't find variable: qq
+ // ex.line =3D 59
+ // ex.sourceId =3D 580238192
+ // ex.sourceURL =3D http://...
+ // ex.expressionBeginOffset =3D 96
+ // ex.expressionCaretOffset =3D 98
+ // ex.expressionEndOffset =3D 98
+ // ex.name =3D ReferenceError
+ //
+ // FIREFOX:
+ // ex.message =3D qq is not defined
+ // ex.fileName =3D http://...
+ // ex.lineNumber =3D 59
+ // ex.stack =3D ...stack trace... (see the example below)
+ // ex.name =3D ReferenceError
+ //
+ // CHROME:
+ // ex.message =3D qq is not defined
+ // ex.name =3D ReferenceError
+ // ex.type =3D not_defined
+ // ex.arguments =3D ['aa']
+ // ex.stack =3D ...stack trace...
+ //
+ // INTERNET EXPLORER:
+ // ex.message =3D ...
+ // ex.name =3D ReferenceError
+ //
+ // OPERA:
+ // ex.message =3D ...message... (see the example below)
+ // ex.name =3D ReferenceError
+ // ex.opera#sourceloc =3D 11 (pretty much useless, duplicates the inf=
o in ex.message)
+ // ex.stacktrace =3D n/a; see 'opera:config#UserPrefs|Exceptions Have =
Stacktrace'
+
+ /**
+ * Computes stack trace information from the stack property.
+ * Chrome and Gecko use this property.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceFromStackProp(ex) {
+ if (!ex.stack) {
+ return null;
+ }
+
+ var chrome =3D /^\s*at (?:((?:\[object object\])?\S+) )?\(?((?:fil=
e|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
+ gecko =3D /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\=
d+)(?::(\d+))?\s*$/i,
+ lines =3D ex.stack.split('\n'),
+ stack =3D [],
+ parts,
+ element,
+ reference =3D /^(.*) is undefined$/.exec(ex.message);
+
+ for (var i =3D 0, j =3D lines.length; i < j; ++i) {
+ if ((parts =3D gecko.exec(lines[i]))) {
+ element =3D {
+ 'url': parts[3],
+ 'func': parts[1] || '?',
+ 'args': parts[2] ? parts[2].split(',') : '',
+ 'line': +parts[4],
+ 'column': parts[5] ? +parts[5] : null
+ };
+ } else if ((parts =3D chrome.exec(lines[i]))) {
+ element =3D {
+ 'url': parts[2],
+ 'func': parts[1] || '?',
+ 'line': +parts[3],
+ 'column': parts[4] ? +parts[4] : null
+ };
+ } else {
+ continue;
+ }
+
+ if (!element.func && element.line) {
+ element.func =3D guessFunctionName(element.url, element.li=
ne);
+ }
+
+ if (element.line) {
+ element.context =3D gatherContext(element.url, element.lin=
e);
+ }
+
+ stack.push(element);
+ }
+
+ if (stack[0] && stack[0].line && !stack[0].column && reference) {
+ stack[0].column =3D findSourceInLine(reference[1], stack[0].ur=
l, stack[0].line);
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ return {
+ 'mode': 'stack',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * Computes stack trace information from the stacktrace property.
+ * Opera 10 uses this property.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceFromStacktraceProp(ex) {
+ // Access and store the stacktrace property before doing ANYTHING
+ // else to it because Opera is not very good at providing it
+ // reliably in other circumstances.
+ var stacktrace =3D ex.stacktrace;
+
+ var testRE =3D / line (\d+), column (\d+) in (?:<anonymous functio=
n: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,
+ lines =3D stacktrace.split('\n'),
+ stack =3D [],
+ parts;
+
+ for (var i =3D 0, j =3D lines.length; i < j; i +=3D 2) {
+ if ((parts =3D testRE.exec(lines[i]))) {
+ var element =3D {
+ 'line': +parts[1],
+ 'column': +parts[2],
+ 'func': parts[3] || parts[4],
+ 'args': parts[5] ? parts[5].split(',') : [],
+ 'url': parts[6]
+ };
+
+ if (!element.func && element.line) {
+ element.func =3D guessFunctionName(element.url, elemen=
t.line);
+ }
+ if (element.line) {
+ try {
+ element.context =3D gatherContext(element.url, ele=
ment.line);
+ } catch (exc) {}
+ }
+
+ if (!element.context) {
+ element.context =3D [lines[i + 1]];
+ }
+
+ stack.push(element);
+ }
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ return {
+ 'mode': 'stacktrace',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * NOT TESTED.
+ * Computes stack trace information from an error message that includes
+ * the stack trace.
+ * Opera 9 and earlier use this method if the option to show stack
+ * traces is turned on in opera:config.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack information.
+ */
+ function computeStackTraceFromOperaMultiLineMessage(ex) {
+ // Opera includes a stack trace into the exception message. An exa=
mple is:
+ //
+ // Statement on line 3: Undefined variable: undefinedFunc
+ // Backtrace:
+ // Line 3 of linked script file://localhost/Users/andreyvit/Proj=
ects/TraceKit/javascript-client/sample.js: In function zzz
+ // undefinedFunc(a);
+ // Line 7 of inline#1 script in file://localhost/Users/andreyvit=
/Projects/TraceKit/javascript-client/sample.html: In function yyy
+ // zzz(x, y, z);
+ // Line 3 of inline#1 script in file://localhost/Users/andreyvit=
/Projects/TraceKit/javascript-client/sample.html: In function xxx
+ // yyy(a, a, a);
+ // Line 1 of function script
+ // try { xxx('hi'); return false; } catch(ex) { TraceKit.repor=
t(ex); }
+ // ...
+
+ var lines =3D ex.message.split('\n');
+ if (lines.length < 4) {
+ return null;
+ }
+
+ var lineRE1 =3D /^\s*Line (\d+) of linked script ((?:file|http|htt=
ps)\S+)(?:: in function (\S+))?\s*$/i,
+ lineRE2 =3D /^\s*Line (\d+) of inline#(\d+) script in ((?:file=
|http|https)\S+)(?:: in function (\S+))?\s*$/i,
+ lineRE3 =3D /^\s*Line (\d+) of function script\s*$/i,
+ stack =3D [],
+ scripts =3D document.getElementsByTagName('script'),
+ inlineScriptBlocks =3D [],
+ parts,
+ i,
+ len,
+ source;
+
+ for (i in scripts) {
+ if (TraceKit._has(scripts, i) && !scripts[i].src) {
+ inlineScriptBlocks.push(scripts[i]);
+ }
+ }
+
+ for (i =3D 2, len =3D lines.length; i < len; i +=3D 2) {
+ var item =3D null;
+ if ((parts =3D lineRE1.exec(lines[i]))) {
+ item =3D {
+ 'url': parts[2],
+ 'func': parts[3],
+ 'line': +parts[1]
+ };
+ } else if ((parts =3D lineRE2.exec(lines[i]))) {
+ item =3D {
+ 'url': parts[3],
+ 'func': parts[4]
+ };
+ var relativeLine =3D (+parts[1]); // relative to the start=
of the <SCRIPT> block
+ var script =3D inlineScriptBlocks[parts[2] - 1];
+ if (script) {
+ source =3D getSource(item.url);
+ if (source) {
+ source =3D source.join('\n');
+ var pos =3D source.indexOf(script.innerText);
+ if (pos >=3D 0) {
+ item.line =3D relativeLine + source.substring(=
0, pos).split('\n').length;
+ }
+ }
+ }
+ } else if ((parts =3D lineRE3.exec(lines[i]))) {
+ var url =3D window.location.href.replace(/#.*$/, ''),
+ line =3D parts[1];
+ var re =3D new RegExp(escapeCodeAsRegExpForMatchingInsideH=
TML(lines[i + 1]));
+ source =3D findSourceInUrls(re, [url]);
+ item =3D {
+ 'url': url,
+ 'line': source ? source.line : line,
+ 'func': ''
+ };
+ }
+
+ if (item) {
+ if (!item.func) {
+ item.func =3D guessFunctionName(item.url, item.line);
+ }
+ var context =3D gatherContext(item.url, item.line);
+ var midline =3D (context ? context[Math.floor(context.leng=
th / 2)] : null);
+ if (context && midline.replace(/^\s*/, '') =3D=3D=3D lines=
[i + 1].replace(/^\s*/, '')) {
+ item.context =3D context;
+ } else {
+ // if (context) alert("Context mismatch. Correct midli=
ne:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + conte=
xt.join("\n") + "\n\nURL:\n" + item.url);
+ item.context =3D [lines[i + 1]];
+ }
+ stack.push(item);
+ }
+ }
+ if (!stack.length) {
+ return null; // could not parse multiline exception message as=
Opera stack trace
+ }
+
+ return {
+ 'mode': 'multiline',
+ 'name': ex.name,
+ 'message': lines[0],
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * Adds information about the first frame to incomplete stack traces.
+ * Safari and IE require this to get complete data on the first frame.
+ * @param {Object.<string, *>} stackInfo Stack trace information from
+ * one of the compute* methods.
+ * @param {string} url The URL of the script that caused an error.
+ * @param {(number|string)} lineNo The line number of the script that
+ * caused an error.
+ * @param {string=3D} message The error generated by the browser, which
+ * hopefully contains the name of the object that caused the error.
+ * @return {boolean} Whether or not the stack information was
+ * augmented.
+ */
+ function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, m=
essage) {
+ var initial =3D {
+ 'url': url,
+ 'line': lineNo
+ };
+
+ if (initial.url && initial.line) {
+ stackInfo.incomplete =3D false;
+
+ if (!initial.func) {
+ initial.func =3D guessFunctionName(initial.url, initial.li=
ne);
+ }
+
+ if (!initial.context) {
+ initial.context =3D gatherContext(initial.url, initial.lin=
e);
+ }
+
+ var reference =3D / '([^']+)' /.exec(message);
+ if (reference) {
+ initial.column =3D findSourceInLine(reference[1], initial.=
url, initial.line);
+ }
+
+ if (stackInfo.stack.length > 0) {
+ if (stackInfo.stack[0].url =3D=3D=3D initial.url) {
+ if (stackInfo.stack[0].line =3D=3D=3D initial.line) {
+ return false; // already in stack trace
+ } else if (!stackInfo.stack[0].line && stackInfo.stack=
[0].func =3D=3D=3D initial.func) {
+ stackInfo.stack[0].line =3D initial.line;
+ stackInfo.stack[0].context =3D initial.context;
+ return false;
+ }
+ }
+ }
+
+ stackInfo.stack.unshift(initial);
+ stackInfo.partial =3D true;
+ return true;
+ } else {
+ stackInfo.incomplete =3D true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes stack trace information by walking the arguments.caller
+ * chain at the time the exception occurred. This will cause earlier
+ * frames to be missed but is the only way to get any stack trace in
+ * Safari and IE. The top frame is restored by
+ * {@link augmentStackTraceWithInitialElement}.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceByWalkingCallerChain(ex, depth) {
+ var functionName =3D /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-=
9\xA0-\uFFFF]*)?\s*\(/i,
+ stack =3D [],
+ funcs =3D {},
+ recursion =3D false,
+ parts,
+ item,
+ source;
+
+ for (var curr =3D computeStackTraceByWalkingCallerChain.caller; cu=
rr && !recursion; curr =3D curr.caller) {
+ if (curr =3D=3D=3D computeStackTrace || curr =3D=3D=3D TraceKi=
t.report) {
+ // console.log('skipping internal function');
+ continue;
+ }
+
+ item =3D {
+ 'url': null,
+ 'func': '?',
+ 'line': null,
+ 'column': null
+ };
+
+ if (curr.name) {
+ item.func =3D curr.name;
+ } else if ((parts =3D functionName.exec(curr.toString()))) {
+ item.func =3D parts[1];
+ }
+
+ if ((source =3D findSourceByFunctionBody(curr))) {
+ item.url =3D source.url;
+ item.line =3D source.line;
+
+ if (item.func =3D=3D=3D '?') {
+ item.func =3D guessFunctionName(item.url, item.line);
+ }
+
+ var reference =3D / '([^']+)' /.exec(ex.message || ex.desc=
ription);
+ if (reference) {
+ item.column =3D findSourceInLine(reference[1], source.=
url, source.line);
+ }
+ }
+
+ if (funcs['' + curr]) {
+ recursion =3D true;
+ }else{
+ funcs['' + curr] =3D true;
+ }
+
+ stack.push(item);
+ }
+
+ if (depth) {
+ // console.log('depth is ' + depth);
+ // console.log('stack is ' + stack.length);
+ stack.splice(0, depth);
+ }
+
+ var result =3D {
+ 'mode': 'callers',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fil=
eName, ex.line || ex.lineNumber, ex.message || ex.description);
+ return result;
+ }
+
+ /**
+ * Computes a stack trace for an exception.
+ * @param {Error} ex
+ * @param {(string|number)=3D} depth
+ */
+ function computeStackTrace(ex, depth) {
+ var stack =3D null;
+ depth =3D (depth =3D=3D null ? 0 : +depth);
+
+ try {
+ // This must be tried first because Opera 10 *destroys*
+ // its stacktrace property if you try to access the stack
+ // property first!!
+ stack =3D computeStackTraceFromStacktraceProp(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack =3D computeStackTraceFromStackProp(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack =3D computeStackTraceFromOperaMultiLineMessage(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack =3D computeStackTraceByWalkingCallerChain(ex, depth + 1);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ return {
+ 'mode': 'failed'
+ };
+ }
+
+ /**
+ * Logs a stacktrace starting from the previous call and working down.
+ * @param {(number|string)=3D} depth How many frames deep to trace.
+ * @return {Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceOfCaller(depth) {
+ depth =3D (depth =3D=3D null ? 0 : +depth) + 1; // "+ 1" because "=
ofCaller" should drop one frame
+ try {
+ throw new Error();
+ } catch (ex) {
+ return computeStackTrace(ex, depth + 1);
+ }
+
+ return null;
+ }
+
+ computeStackTrace.augmentStackTraceWithInitialElement =3D augmentStack=
TraceWithInitialElement;
+ computeStackTrace.guessFunctionName =3D guessFunctionName;
+ computeStackTrace.gatherContext =3D gatherContext;
+ computeStackTrace.ofCaller =3D computeStackTraceOfCaller;
+
+ return computeStackTrace;
+}());
+
+/**
+ * Extends support for global error handling for asynchronous browser
+ * functions. Adopted from Closure Library's errorhandler.js
+ */
+(function extendToAsynchronousCallbacks(w) {
+ var _helper =3D function _helper(fnName) {
+ var originalFn =3D w[fnName];
+ w[fnName] =3D function traceKitAsyncExtension() {
+ // Make a copy of the arguments
+ var args =3D Array.prototype.slice.call(arguments, 0);
+ var originalCallback =3D args[0];
+ if (typeof (originalCallback) =3D=3D=3D 'function') {
+ args[0] =3D function traceKitArgsZero() {
+ try {
+ originalCallback.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ }
+ // IE < 9 doesn't support .call/.apply on setInterval/setTimeo=
ut, but it
+ // also only supports 2 argument and doesn't care what "this" =
is, so we
+ // can just call the original function directly.
+ if (originalFn.apply) {
+ return originalFn.apply(this, args);
+ } else {
+ return originalFn(args[0], args[1]);
+ }
+ };
+ };
+
+ _helper('setTimeout');
+ _helper('setInterval');
+}(window));
+
+/**
+ * Extended support for backtraces and global error handling for most
+ * asynchronous jQuery functions.
+ */
+(function traceKitAsyncForjQuery($) {
+
+ // quit if jQuery isn't on the page
+ if (!$) {
+ return;
+ }
+
+ var _oldEventAdd =3D $.event.add;
+ $.event.add =3D function traceKitEventAdd(elem, types, handler, data, =
selector) {
+ var _handler;
+
+ if (handler.handler) {
+ _handler =3D handler.handler;
+ handler.handler =3D function traceKitHandler() {
+ try {
+ return _handler.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ } else {
+ _handler =3D handler;
+ handler =3D function apply_handler() {
+ try {
+ return _handler.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ }
+
+ // If the handler we are attaching doesn=E2=80=99t have the same g=
uid as
+ // the original, it will never be removed when someone tries to
+ // unbind the original function later. Technically as a result of
+ // this our guids are no longer globally unique, but whatever, that
+ // never hurt anybody RIGHT?!
+ if (_handler.guid) {
+ handler.guid =3D _handler.guid;
+ } else {
+ handler.guid =3D _handler.guid =3D $.guid++;
+ }
+
+ return _oldEventAdd.call(this, elem, types, handler, data, selecto=
r);
+ };
+
+ var _oldReady =3D $.fn.ready;
+ $.fn.ready =3D function traceKitjQueryReadyWrapper(fn) {
+ var _fn =3D function () {
+ try {
+ return fn.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+
+ return _oldReady.call(this, _fn);
+ };
+
+ var _oldAjax =3D $.ajax;
+ $.ajax =3D function traceKitAjaxWrapper(s) {
+ if ($.isFunction(s.complete)) {
+ var _oldComplete =3D s.complete;
+ s.complete =3D function traceKitjQueryComplete() {
+ try {
+ return _oldComplete.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ }
+
+ if ($.isFunction(s.error)) {
+ var _oldError =3D s.error;
+ s.error =3D function traceKitjQueryError() {
+ try {
+ return _oldError.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ }
+
+ if ($.isFunction(s.success)) {
+ var _oldSuccess =3D s.success;
+ s.success =3D function traceKitjQuerySuccess() {
+ try {
+ return _oldSuccess.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+ }
+
+ try {
+ return _oldAjax.call(this, s);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ };
+
+}(window.jQuery));
+
+//Default options:
+if (!TraceKit.remoteFetching) {
+ TraceKit.remoteFetching =3D true;
+}
+if (!TraceKit.collectWindowErrors) {
+ TraceKit.collectWindowErrors =3D true;
+}
+if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
+ // 5 lines before, the offending line, 5 lines after
+ TraceKit.linesOfContext =3D 11;
+}
+
+
+
+// Export to global object
+window.TraceKit =3D TraceKit;
+
+}(window));
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 static/scripts/packed/libs/raven.js
--- /dev/null
+++ b/static/scripts/packed/libs/raven.js
@@ -0,0 +1,1 @@
+(function(){var x=3Dwindow.Raven,u=3D!h(window.JSON),e,o,p,q,f=3D{logger:"=
javascript",ignoreErrors:[],ignoreUrls:[]};var z=3DTraceKit.noConflict();z.=
remoteFetching=3Dfalse;var n=3D{VERSION:"@VERSION",noConflict:function(){wi=
ndow.Raven=3Dx;return n},config:function(C,B){var D=3Dv(C),A=3DD.path.lastI=
ndexOf("/"),E=3DD.path.substr(1,A);if(B){j(B,function(F,G){f[F]=3DG})}f.ign=
oreErrors.push("Script error.");p=3DD.user;q=3D~~D.path.substr(A+1);e=3DD.p=
rotocol+"://"+D.host+(D.port?":"+D.port:"")+"/"+E+"api/"+q+"/store/";if(f.f=
etchContext){z.remoteFetching=3Dtrue}return n},install:function(){if(!s()){=
return}z.report.subscribe(m);return n},context:function(B,C,A){if(c(B)){A=
=3DC;C=3DB;B=3Dundefined}n.wrap(B,C).apply(this,A)},wrap:function(A,B){if(c=
(A)){B=3DA;A=3Dundefined}return function(){try{B.apply(this,arguments)}catc=
h(C){n.captureException(C,A)}}},uninstall:function(){z.report.unsubscribe(m=
);return n},captureException:function(B,A){try{z.report(B,A)}catch(C){if(B!=
=3D=3DC){throw C}}return n},captureMessage:function(B,A){l(r({message:B},A)=
);return n},setUser:function(A){o=3DA;return n}};var b=3D"source protocol a=
uthority userInfo user password host port relative path directory file quer=
y anchor".split(" "),a=3D/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(=
(?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\=
/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;funct=
ion v(D){var A=3Da.exec(D),C=3D{},B=3D14;while(B--){C[b[B]]=3DA[B]||""}retu=
rn C}function h(A){return typeof A=3D=3D=3D"undefined"}function c(A){return=
typeof A=3D=3D=3D"function"}function j(C,D){var B,A;if(C.length=3D=3D=3Dun=
defined){for(B in C){if(C.hasOwnProperty(B)){D.call(null,B,C[B])}}}else{for=
(B=3D0,A=3DC.length;B<A;B++){D.call(null,B,C[B])}}}var g;function d(){if(g)=
{return g}var A=3D["sentry_version=3D2.0","sentry_client=3Draven-js/"+n.VER=
SION];if(p){A.push("sentry_key=3D"+p)}g=3D"?"+A.join("&");return g}function=
m(D,B){var F=3D[],C=3D0,A=3DD.stack&&D.stack.length||0,E;for(;C<A;C++){E=
=3Dk(D.stack[C]);if(E){F.push(E)}}i(D.name,D.message,D.url,D.lineno,F,B)}fu=
nction k(E){if(!E.url){return}var D=3D{filename:E.url,lineno:E.line,colno:E=
.column,"function":E.func||"?"},B=3Dw(E);if(B){var A=3D3,C=3D["pre_context"=
,"context_line","post_context"];while(A--){D[C[A]]=3DB[A]}}D.in_app=3D!/(Ra=
ven|TraceKit)\./.test(D["function"]);return D}function w(E){if(!E.context||=
!f.fetchContext){return}var C=3DE.context,A=3D~~(C.length/2),B=3DC.length,D=
=3Dfalse;while(B--){if(C[B].length>300){D=3Dtrue;break}}if(D){if(h(E.column=
)){return}return[[],C[A].substr(E.column,50),[]]}return[C.slice(0,A),C[A],C=
.slice(A+1)]}function i(D,H,F,A,C,I){var G,E,B;B=3Df.ignoreErrors.length;wh=
ile(B--){if(H=3D=3D=3Df.ignoreErrors[B]){return}}if(C&&C.length){G=3D{frame=
s:C};F=3DF||C[0].filename}else{if(F){G=3D{frames:[{filename:F,lineno:A}]}}}=
B=3Df.ignoreUrls.length;while(B--){if(f.ignoreUrls[B].test(F)){return}}E=3D=
A?H+" at "+A:H;l(r({"sentry.interfaces.Exception":{type:D,value:H},"sentry.=
interfaces.Stacktrace":G,culprit:F,message:E},I))}function r(B,A){if(!A){re=
turn B}j(A,function(C,D){B[C]=3DD});return B}function y(){var A=3D{url:wind=
ow.location.href,headers:{"User-Agent":navigator.userAgent}};if(window.docu=
ment.referrer){A.headers.Referer=3Dwindow.document.referrer}return A}functi=
on l(A){if(!s()){return}A=3Dr({project:q,logger:f.logger,site:f.site,platfo=
rm:"javascript","sentry.interfaces.Http":y()},A);if(o){A["sentry.interfaces=
.User"]=3Do}if(c(f.dataCallback)){A=3Df.dataCallback(A)}t(A)}function t(A){=
new Image().src=3De+d()+"&sentry_data=3D"+encodeURIComponent(JSON.stringify=
(A))}function s(){if(!u){return false}if(!e){console.error("Error: Raven ha=
s not been configured.");return false}return true}window.Raven=3Dn})();
\ No newline at end of file
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 static/scripts/packed/libs/tracekit.js
--- /dev/null
+++ b/static/scripts/packed/libs/tracekit.js
@@ -0,0 +1,1 @@
+(function(h,d){var e=3D{};var j=3Dh.TraceKit;e.noConflict=3Dfunction g(){h=
.TraceKit=3Dj;return e};e._has=3Dfunction a(k,l){return Object.prototype.ha=
sOwnProperty.call(k,l)};e.report=3D(function i(){var k=3D[],o=3Dnull,q=3Dnu=
ll;function l(t){k.push(t)}function r(u){for(var t=3Dk.length-1;t>=3D0;--t)=
{if(k[t]=3D=3D=3Du){k.splice(t,1)}}}function p(t,v){var x=3Dnull;if(v&&!e.c=
ollectWindowErrors){return}for(var w in k){if(e._has(k,w)){try{k[w].apply(n=
ull,[t].concat(Array.prototype.slice.call(arguments,2)))}catch(u){x=3Du}}}i=
f(x){throw x}}var s=3Dh.onerror;h.onerror=3Dfunction n(w,v,x){var t=3Dnull;=
if(q){e.computeStackTrace.augmentStackTraceWithInitialElement(q,v,x,w);t=3D=
q;q=3Dnull;o=3Dnull}else{var u=3D{url:v,line:x};u.func=3De.computeStackTrac=
e.guessFunctionName(u.url,u.line);u.context=3De.computeStackTrace.gatherCon=
text(u.url,u.line);t=3D{mode:"onerror",message:w,url:document.location.href=
,stack:[u],useragent:navigator.userAgent}}p(t,"from window.onerror");if(s){=
return s.apply(this,arguments)}return false};function m(v){var u=3DArray.pr=
ototype.slice.call(arguments,1);if(q){if(o=3D=3D=3Dv){return}else{var w=3Dq=
;q=3Dnull;o=3Dnull;p.apply(null,[w,null].concat(u))}}var t=3De.computeStack=
Trace(v);q=3Dt;o=3Dv;h.setTimeout(function(){if(o=3D=3D=3Dv){q=3Dnull;o=3Dn=
ull;p.apply(null,[t,null].concat(u))}},(t.incomplete?2000:0));throw v}m.sub=
scribe=3Dl;m.unsubscribe=3Dr;return m}());e.computeStackTrace=3D(function b=
(){var u=3Dfalse,q=3D{};function l(D){if(!e.remoteFetching){return""}try{va=
r C;if(typeof(XMLHttpRequest)=3D=3D=3D"undefined"){C=3Dfunction E(){try{ret=
urn new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(H){}try{return new Active=
XObject("Msxml2.XMLHTTP.3.0")}catch(H){}try{return new ActiveXObject("Msxml=
2.XMLHTTP")}catch(H){}try{return new ActiveXObject("Microsoft.XMLHTTP")}cat=
ch(H){}throw new Error("No XHR.")}}else{C=3DXMLHttpRequest}var F=3Dnew C();=
F.open("GET",D,false);F.send("");return F.responseText}catch(G){return""}}f=
unction t(C){if(!e._has(q,C)){var D;if(C.indexOf(document.domain)!=3D=3D-1)=
{D=3Dl(C)}else{D=3D[]}q[C]=3DD.length?D.split("\n"):[]}return q[C]}function=
z(D,F){var J=3D/function ([^(]*)\(([^)]*)\)/,G=3D/['"]?([0-9A-Za-z$_]+)['"=
]?\s*[:=3D]\s*(function|eval|new Function)/,K=3D"",I=3D10,C=3Dt(D),E;if(!C.=
length){return"?"}for(var H=3D0;H<I;++H){K=3DC[F-H]+K;if(K!=3D=3Dd){if((E=
=3DG.exec(K))){return E[1]}else{if((E=3DJ.exec(K))){return E[1]}}}}return"?=
"}function B(E,K){var D=3Dt(E);if(!D.length){return null}var G=3D[],J=3DMat=
h.floor(e.linesOfContext/2),C=3DJ+(e.linesOfContext%2),F=3DMath.max(0,K-J-1=
),H=3DMath.min(D.length,K+C-1);K-=3D1;for(var I=3DF;I<H;++I){if(typeof(D[I]=
)!=3D=3D"undefined"){G.push(D[I])}}return G.length>0?G:null}function m(C){r=
eturn C.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g,"\\$&")}function x(C){return m(=
C).replace("<","(?:<|<)").replace(">","(?:>|>)").replace("&","(?:&|&a=
mp;)").replace('"','(?:"|")').replace(/\s+/g,"\\s+")}function o(F,H){v=
ar G,C;for(var E=3D0,D=3DH.length;E<D;++E){if((G=3Dt(H[E])).length){G=3DG.j=
oin("\n");if((C=3DF.exec(G))){return{url:H[E],line:G.substring(0,C.index).s=
plit("\n").length,column:C.index-G.lastIndexOf("\n",C.index)-1}}}}return nu=
ll}function A(F,E,D){var H=3Dt(E),G=3Dnew RegExp("\\b"+m(F)+"\\b"),C;D-=3D1=
;if(H&&H.length>D&&(C=3DG.exec(H[D]))){return C.index}return null}function =
v(H){var N=3D[h.location.href],I=3Ddocument.getElementsByTagName("script"),=
L,F=3D""+H,E=3D/^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]=
*\S)\s*\}\s*$/,G=3D/^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s=
*\}\s*$/,P,J,Q;for(var K=3D0;K<I.length;++K){var O=3DI[K];if(O.src){N.push(=
O.src)}}if(!(J=3DE.exec(F))){P=3Dnew RegExp(m(F).replace(/\s+/g,"\\s+"))}el=
se{var D=3DJ[1]?"\\s+"+J[1]:"",M=3DJ[2].split(",").join("\\s*,\\s*");L=3Dm(=
J[3]).replace(/;$/,";?");P=3Dnew RegExp("function"+D+"\\s*\\(\\s*"+M+"\\s*\=
\)\\s*{\\s*"+L+"\\s*}")}if((Q=3Do(P,N))){return Q}if((J=3DG.exec(F))){var C=
=3DJ[1];L=3Dx(J[2]);P=3Dnew RegExp("on"+C+"=3D[\\'\"]\\s*"+L+"\\s*[\\'\"]",=
"i");if((Q=3Do(P,N[0]))){return Q}P=3Dnew RegExp(L);if((Q=3Do(P,N))){return=
Q}}return null}function w(J){if(!J.stack){return null}var I=3D/^\s*at (?:(=
(?:\[object object\])?\S+) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\=
)?\s*$/i,C=3D/^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d=
+))?\s*$/i,L=3DJ.stack.split("\n"),K=3D[],F,H,D=3D/^(.*) is undefined$/.exe=
c(J.message);for(var G=3D0,E=3DL.length;G<E;++G){if((F=3DC.exec(L[G]))){H=
=3D{url:F[3],func:F[1]||"?",args:F[2]?F[2].split(","):"",line:+F[4],column:=
F[5]?+F[5]:null}}else{if((F=3DI.exec(L[G]))){H=3D{url:F[2],func:F[1]||"?",l=
ine:+F[3],column:F[4]?+F[4]:null}}else{continue}}if(!H.func&&H.line){H.func=
=3Dz(H.url,H.line)}if(H.line){H.context=3DB(H.url,H.line)}K.push(H)}if(K[0]=
&&K[0].line&&!K[0].column&&D){K[0].column=3DA(D[1],K[0].url,K[0].line)}if(!=
K.length){return null}return{mode:"stack",name:J.name,message:J.message,url=
:document.location.href,stack:K,useragent:navigator.userAgent}}function s(H=
){var J=3DH.stacktrace;var G=3D/ line (\d+), column (\d+) in (?:<anonymous =
function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,L=3DJ.split("\n"),I=3D[=
],C;for(var F=3D0,D=3DL.length;F<D;F+=3D2){if((C=3DG.exec(L[F]))){var E=3D{=
line:+C[1],column:+C[2],func:C[3]||C[4],args:C[5]?C[5].split(","):[],url:C[=
6]};if(!E.func&&E.line){E.func=3Dz(E.url,E.line)}if(E.line){try{E.context=
=3DB(E.url,E.line)}catch(K){}}if(!E.context){E.context=3D[L[F+1]]}I.push(E)=
}}if(!I.length){return null}return{mode:"stacktrace",name:H.name,message:H.=
message,url:document.location.href,stack:I,useragent:navigator.userAgent}}f=
unction p(U){var E=3DU.message.split("\n");if(E.length<4){return null}var G=
=3D/^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in functio=
n (\S+))?\s*$/i,F=3D/^\s*Line (\d+) of inline#(\d+) script in ((?:file|http=
|https)\S+)(?:: in function (\S+))?\s*$/i,C=3D/^\s*Line (\d+) of function s=
cript\s*$/i,L=3D[],I=3Ddocument.getElementsByTagName("script"),T=3D[],P,R,S=
,Q;for(R in I){if(e._has(I,R)&&!I[R].src){T.push(I[R])}}for(R=3D2,S=3DE.len=
gth;R<S;R+=3D2){var V=3Dnull;if((P=3DG.exec(E[R]))){V=3D{url:P[2],func:P[3]=
,line:+P[1]}}else{if((P=3DF.exec(E[R]))){V=3D{url:P[3],func:P[4]};var D=3D(=
+P[1]);var W=3DT[P[2]-1];if(W){Q=3Dt(V.url);if(Q){Q=3DQ.join("\n");var K=3D=
Q.indexOf(W.innerText);if(K>=3D0){V.line=3DD+Q.substring(0,K).split("\n").l=
ength}}}}else{if((P=3DC.exec(E[R]))){var J=3Dh.location.href.replace(/#.*$/=
,""),M=3DP[1];var O=3Dnew RegExp(x(E[R+1]));Q=3Do(O,[J]);V=3D{url:J,line:Q?=
Q.line:M,func:""}}}}if(V){if(!V.func){V.func=3Dz(V.url,V.line)}var H=3DB(V.=
url,V.line);var N=3D(H?H[Math.floor(H.length/2)]:null);if(H&&N.replace(/^\s=
*/,"")=3D=3D=3DE[R+1].replace(/^\s*/,"")){V.context=3DH}else{V.context=3D[E=
[R+1]]}L.push(V)}}if(!L.length){return null}return{mode:"multiline",name:U.=
name,message:E[0],url:document.location.href,stack:L,useragent:navigator.us=
erAgent}}function y(G,E,H,F){var D=3D{url:E,line:H};if(D.url&&D.line){G.inc=
omplete=3Dfalse;if(!D.func){D.func=3Dz(D.url,D.line)}if(!D.context){D.conte=
xt=3DB(D.url,D.line)}var C=3D/ '([^']+)' /.exec(F);if(C){D.column=3DA(C[1],=
D.url,D.line)}if(G.stack.length>0){if(G.stack[0].url=3D=3D=3DD.url){if(G.st=
ack[0].line=3D=3D=3DD.line){return false}else{if(!G.stack[0].line&&G.stack[=
0].func=3D=3D=3DD.func){G.stack[0].line=3DD.line;G.stack[0].context=3DD.con=
text;return false}}}}G.stack.unshift(D);G.partial=3Dtrue;return true}else{G=
.incomplete=3Dtrue}return false}function n(J,H){var I=3D/function\s+([_$a-z=
A-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,K=3D[],D=3D{},F=3Dfalse,G=
,L,C;for(var N=3Dn.caller;N&&!F;N=3DN.caller){if(N=3D=3D=3Dr||N=3D=3D=3De.r=
eport){continue}L=3D{url:null,func:"?",line:null,column:null};if(N.name){L.=
func=3DN.name}else{if((G=3DI.exec(N.toString()))){L.func=3DG[1]}}if((C=3Dv(=
N))){L.url=3DC.url;L.line=3DC.line;if(L.func=3D=3D=3D"?"){L.func=3Dz(L.url,=
L.line)}var E=3D/ '([^']+)' /.exec(J.message||J.description);if(E){L.column=
=3DA(E[1],C.url,C.line)}}if(D[""+N]){F=3Dtrue}else{D[""+N]=3Dtrue}K.push(L)=
}if(H){K.splice(0,H)}var M=3D{mode:"callers",name:J.name,message:J.message,=
url:document.location.href,stack:K,useragent:navigator.userAgent};y(M,J.sou=
rceURL||J.fileName,J.line||J.lineNumber,J.message||J.description);return M}=
function r(D,F){var C=3Dnull;F=3D(F=3D=3Dnull?0:+F);try{C=3Ds(D);if(C){retu=
rn C}}catch(E){if(u){throw E}}try{C=3Dw(D);if(C){return C}}catch(E){if(u){t=
hrow E}}try{C=3Dp(D);if(C){return C}}catch(E){if(u){throw E}}try{C=3Dn(D,F+=
1);if(C){return C}}catch(E){if(u){throw E}}return{mode:"failed"}}function k=
(D){D=3D(D=3D=3Dnull?0:+D)+1;try{throw new Error()}catch(C){return r(C,D+1)=
}return null}r.augmentStackTraceWithInitialElement=3Dy;r.guessFunctionName=
=3Dz;r.gatherContext=3DB;r.ofCaller=3Dk;return r}());(function f(k){var l=
=3Dfunction l(o){var n=3Dk[o];k[o]=3Dfunction m(){var p=3DArray.prototype.s=
lice.call(arguments,0);var r=3Dp[0];if(typeof(r)=3D=3D=3D"function"){p[0]=
=3Dfunction q(){try{r.apply(this,arguments)}catch(s){e.report(s);throw s}}}=
if(n.apply){return n.apply(this,p)}else{return n(p[0],p[1])}}};l("setTimeou=
t");l("setInterval")}(h));(function c(q){if(!q){return}var p=3Dq.event.add;=
q.event.add=3Dfunction n(w,u,v,x,r){var s;if(v.handler){s=3Dv.handler;v.han=
dler=3Dfunction t(){try{return s.apply(this,arguments)}catch(z){e.report(z)=
;throw z}}}else{s=3Dv;v=3Dfunction y(){try{return s.apply(this,arguments)}c=
atch(z){e.report(z);throw z}}}if(s.guid){v.guid=3Ds.guid}else{v.guid=3Ds.gu=
id=3Dq.guid++}return p.call(this,w,u,v,x,r)};var l=3Dq.fn.ready;q.fn.ready=
=3Dfunction k(r){var s=3Dfunction(){try{return r.apply(this,arguments)}catc=
h(t){e.report(t);throw t}};return l.call(this,s)};var o=3Dq.ajax;q.ajax=3Df=
unction m(x){if(q.isFunction(x.complete)){var v=3Dx.complete;x.complete=3Df=
unction u(){try{return v.apply(this,arguments)}catch(s){e.report(s);throw s=
}}}if(q.isFunction(x.error)){var r=3Dx.error;x.error=3Dfunction z(){try{ret=
urn r.apply(this,arguments)}catch(s){e.report(s);throw s}}}if(q.isFunction(=
x.success)){var w=3Dx.success;x.success=3Dfunction t(){try{return w.apply(t=
his,arguments)}catch(s){e.report(s);throw s}}}try{return o.call(this,x)}cat=
ch(y){e.report(y);throw y}}}(h.jQuery));if(!e.remoteFetching){e.remoteFetch=
ing=3Dtrue}if(!e.collectWindowErrors){e.collectWindowErrors=3Dtrue}if(!e.li=
nesOfContext||e.linesOfContext<1){e.linesOfContext=3D11}h.TraceKit=3De}(win=
dow));
\ No newline at end of file
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 templates/base.mako
--- a/templates/base.mako
+++ b/templates/base.mako
@@ -24,6 +24,17 @@
## Default javascripts
<%def name=3D"javascripts()">
=20
+ ## Send errors to Sntry server if configured
+ %if app.config.sentry_dsn:
+ ${h.js( "libs/tracekit", "libs/raven" )}
+ <script>
+ Raven.config('${app.config.sentry_dsn_public}').install();
+ %if trans.user:
+ Raven.setUser( { email: "${trans.user.email}" } );
+ %endif
+ </script>
+ %endif
+
${h.js(
"libs/jquery/jquery",
"libs/jquery/select2",
@@ -54,7 +65,7 @@
error : function(){},
assert : function(){}
};
- =20
+
// Set up needed paths.
var galaxy_paths =3D new GalaxyPaths({
root_path: '${h.url_for( "/" )}',
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 templates/base/base_panels.mako
--- a/templates/base/base_panels.mako
+++ b/templates/base/base_panels.mako
@@ -45,9 +45,18 @@
=20
## Default javascripts
<%def name=3D"javascripts()">
- <!--[if lt IE 7]>
- ${h.js( 'libs/IE/IE7', 'libs/IE/ie7-recalc' )}
- <![endif]-->
+
+ ## Send errors to Sntry server if configured
+ %if app.config.sentry_dsn:
+ ${h.js( "libs/tracekit", "libs/raven" )}
+ <script>
+ Raven.config('${app.config.sentry_dsn_public}').install();
+ %if trans.user:
+ Raven.setUser( { email: "${trans.user.email}" } );
+ %endif
+ </script>
+ %endif
+
${h.js(
'libs/jquery/jquery',
'libs/json2',
diff -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 -r 708d0ccee63fc75eb88e5dc=
3a9e0d6d3e5ff72e7 templates/root/tool_menu.mako
--- a/templates/root/tool_menu.mako
+++ b/templates/root/tool_menu.mako
@@ -74,7 +74,7 @@
});
*/
=20
- $( '.tooltip' ).tooltip();
+ $( '.tooltip' ).tooltip().foasdasdasdoo();
=20
// TODO: is this necessary?
$( "a[minsizehint]" ).click( function() {
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: jobs: Fix LWR after merge (merged backwards?)
by Bitbucket 02 Feb '13
by Bitbucket 02 Feb '13
02 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7950ce95b276/
changeset: 7950ce95b276
user: james_taylor
date: 2013-02-02 22:37:28
summary: jobs: Fix LWR after merge (merged backwards?)
affected #: 1 file
diff -r fa1a29005dcaf5e3146f063f0bdc1fea36a7549c -r 7950ce95b2769f2fb63bfc8a549ee9bc9fdcbed2 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -41,227 +41,7 @@
return None
return job_state
- job_config = client.setup()
-
- self.new_working_directory = job_config['working_directory']
- self.new_outputs_directory = job_config['outputs_directory']
- self.remote_path_separator = job_config['path_separator']
-
- self.__initialize_referenced_tool_files()
- self.__upload_tool_files()
- self.__upload_input_files()
- self.__initialize_output_file_renames()
- self.__initialize_config_file_renames()
- self.__rewrite_and_upload_config_files()
- self.__rewrite_command_line()
-
- def __initialize_referenced_tool_files(self):
- pattern = r"(%s%s\S+)" % (self.tool_dir, os.sep)
- referenced_tool_files = []
- referenced_tool_files += re.findall(pattern, self.command_line)
- if self.config_files != None:
- for config_file in self.config_files:
- referenced_tool_files += re.findall(pattern, self.__read(config_file))
- self.referenced_tool_files = referenced_tool_files
-
- def __upload_tool_files(self):
- for referenced_tool_file in self.referenced_tool_files:
- tool_upload_response = self.client.upload_tool_file(referenced_tool_file)
- self.file_renames[referenced_tool_file] = tool_upload_response['path']
-
- def __upload_input_files(self):
- for input_file in self.input_files:
- input_upload_response = self.client.upload_input(input_file)
- self.file_renames[input_file] = input_upload_response['path']
-
- def __initialize_output_file_renames(self):
- for output_file in self.output_files:
- self.file_renames[output_file] = r'%s%s%s' % (self.new_outputs_directory,
- self.remote_path_separator,
- os.path.basename(output_file))
-
- def __initialize_config_file_renames(self):
- for config_file in self.config_files:
- self.file_renames[config_file] = r'%s%s%s' % (self.new_working_directory,
- self.remote_path_separator,
- os.path.basename(config_file))
-
- def __rewrite_paths(self, contents):
- new_contents = contents
- for local_path, remote_path in self.file_renames.iteritems():
- new_contents = new_contents.replace(local_path, remote_path)
- return new_contents
-
- def __rewrite_and_upload_config_files(self):
- for config_file in self.config_files:
- config_contents = self.__read(config_file)
- new_config_contents = self.__rewrite_paths(config_contents)
- self.client.upload_config_file(config_file, new_config_contents)
-
- def __rewrite_command_line(self):
- self.rewritten_command_line = self.__rewrite_paths(self.command_line)
-
- def get_rewritten_command_line(self):
- return self.rewritten_command_line
-
- def __read(self, path):
- input = open(path, "r")
- try:
- return input.read()
- finally:
- input.close()
-
-
-
-class Client(object):
- """
- """
- """
- """
- def __init__(self, remote_host, job_id, private_key=None):
- if not remote_host.endswith("/"):
- remote_host = remote_host + "/"
- ## If we don't have an explicit private_key defined, check for
- ## one embedded in the URL. A URL of the form
- ## https://moo@cow:8913 will try to contact https://cow:8913
- ## with a private key of moo
- private_key_format = "https?://(.*)@.*/?"
- private_key_match= re.match(private_key_format, remote_host)
- if not private_key and private_key_match:
- private_key = private_key_match.group(1)
- remote_host = remote_host.replace("%s@" % private_key, '', 1)
- self.remote_host = remote_host
- self.job_id = job_id
- self.private_key = private_key
-
- def url_open(self, request, data):
- return urllib2.urlopen(request, data)
-
- def __build_url(self, command, args):
- if self.private_key:
- args["private_key"] = self.private_key
- data = urllib.urlencode(args)
- url = self.remote_host + command + "?" + data
- return url
-
- def __raw_execute(self, command, args = {}, data = None):
- url = self.__build_url(command, args)
- request = urllib2.Request(url=url, data=data)
- response = self.url_open(request, data)
- return response
-
- def __raw_execute_and_parse(self, command, args = {}, data = None):
- response = self.__raw_execute(command, args, data)
- return simplejson.loads(response.read())
-
- def __upload_file(self, action, path, contents = None):
- """ """
- input = open(path, 'rb')
- try:
- mmapped_input = mmap.mmap(input.fileno(), 0, access = mmap.ACCESS_READ)
- return self.__upload_contents(action, path, mmapped_input)
- finally:
- input.close()
-
- def __upload_contents(self, action, path, contents):
- name = os.path.basename(path)
- args = {"job_id" : self.job_id, "name" : name}
- return self.__raw_execute_and_parse(action, args, contents)
-
- def upload_tool_file(self, path):
- return self.__upload_file("upload_tool_file", path)
-
- def upload_input(self, path):
- return self.__upload_file("upload_input", path)
-
- def upload_config_file(self, path, contents):
- return self.__upload_contents("upload_config_file", path, contents)
-
- def download_output(self, path):
- """ """
- name = os.path.basename(path)
- response = self.__raw_execute('download_output', {'name' : name,
- "job_id" : self.job_id})
- output = open(path, 'wb')
- try:
- while True:
- buffer = response.read(1024)
- if buffer == "":
- break
- output.write(buffer)
- finally:
- output.close()
-
- def launch(self, command_line):
- """ """
- return self.__raw_execute("launch", {"command_line" : command_line,
- "job_id" : self.job_id})
-
- def kill(self):
- return self.__raw_execute("kill", {"job_id" : self.job_id})
-
- def wait(self):
- """ """
- while True:
- check_complete_response = self.__raw_execute_and_parse("check_complete", {"job_id" : self.job_id })
- complete = check_complete_response["complete"] == "true"
- if complete:
- return check_complete_response
- time.sleep(1)
-
- def clean(self):
- self.__raw_execute("clean", { "job_id" : self.job_id })
-
- def setup(self):
- return self.__raw_execute_and_parse("setup", { "job_id" : self.job_id })
-
-
-
-class LwrJobRunner( BaseJobRunner ):
- """
- Lwr Job Runner
- """
- STOP_SIGNAL = object()
- def __init__( self, app ):
- """Start the job runner with 'nworkers' worker threads"""
- self.app = app
- self.sa_session = app.model.context
-
- # start workers
- self.queue = Queue()
- self.threads = []
- 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.setDaemon( True )
- worker.start()
- self.threads.append( worker )
- log.debug( "%d workers ready", nworkers )
-
- def run_next( self ):
- """Run the next job, waiting until one is available if neccesary"""
- while 1:
- job_wrapper = self.queue.get()
- if job_wrapper is self.STOP_SIGNAL:
- return
- try:
- self.run_job( job_wrapper )
- except:
- log.exception( "Uncaught exception running job" )
-
- def determine_lwr_url(self, url):
- lwr_url = url[ len( 'lwr://' ) : ]
- return lwr_url
-
- def get_client_from_wrapper(self, job_wrapper):
- return self.get_client( job_wrapper.get_job_runner_url(), job_wrapper.job_id )
-
- def get_client(self, job_runner, job_id):
- lwr_url = self.determine_lwr_url( job_runner )
- return Client(lwr_url, job_id)
-
- def run_job( self, job_wrapper ):
+ def queue_job(self, job_wrapper):
stderr = stdout = command_line = ''
runner_url = job_wrapper.get_job_runner_url()
@@ -458,4 +238,4 @@
elif job.get_state() == model.Job.states.QUEUED:
# LWR doesn't queue currently, so this indicates galaxy was shutoff while
# job was being staged. Not sure how to recover from that.
- job_state.job_wrapper.fail( "This job was killed when Galaxy was restarted. Please retry the job." )
+ job_state.job_wrapper.fail( "This job was killed when Galaxy was restarted. Please retry the job." )
\ 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
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