galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
December 2014
- 2 participants
- 245 discussions
commit/galaxy-central: natefoo: Update tag latest_2014.10.06 for changeset 212e1d5e9be5
by commits-noreply@bitbucket.org 11 Dec '14
by commits-noreply@bitbucket.org 11 Dec '14
11 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/5fc83b69fe24/
Changeset: 5fc83b69fe24
Branch: stable
User: natefoo
Date: 2014-12-11 14:00:20+00:00
Summary: Update tag latest_2014.10.06 for changeset 212e1d5e9be5
Affected #: 1 file
diff -r 212e1d5e9be5a9a1e12c834bd545de504753c9fe -r 5fc83b69fe241d469ad27235a39e93d847764bbc .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -20,4 +20,4 @@
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
548ab24667d6206780237bd807f7d857a484c461 latest_2014.08.11
2092948937ac30ef82f71463a235c66d34987088 release_2014.10.06
-3e7adbbe91a06d30a96e7a7101707e040376aba1 latest_2014.10.06
+212e1d5e9be5a9a1e12c834bd545de504753c9fe latest_2014.10.06
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: jmchilton: Refactoring: Start abstracting XML processing out of Tool and param classes.
by commits-noreply@bitbucket.org 10 Dec '14
by commits-noreply@bitbucket.org 10 Dec '14
10 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2dc383cbb700/
Changeset: 2dc383cbb700
User: jmchilton
Date: 2014-12-11 05:26:13+00:00
Summary: Refactoring: Start abstracting XML processing out of Tool and param classes.
Long term this could allow Galaxy to support - multiple tooling formats (Galaxy-like YAML, CWL http://bit.ly/cwltooldesc, etc...). But I think it is also important from a purely design perspective - this is a core logic class integrating different components - they should not also be doing XML parsing.
To verify the interface for parsing tools is expressive enough to allow multiple useful implementations, I built a test YAML tool description that implements many of the same features as Galaxy but smooths out rough edges (uses exit codes for job failure by default for instance). Loading these tools is disabled by default and it is not documented how to enable them because they are not intended to be part of Galaxy's public API.
Affected #: 22 files
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -200,6 +200,9 @@
# workflows built using these modules may not function in the
# future.
self.enable_beta_workflow_modules = string_as_bool( kwargs.get( 'enable_beta_workflow_modules', 'False' ) )
+ # These are not even beta - just experiments - don't use them unless
+ # you want yours tools to be broken in the future.
+ self.enable_beta_tool_formats = string_as_bool( kwargs.get( 'enable_beta_tool_formats', 'False' ) )
# Certain modules such as the pause module will automatically cause
# workflows to be scheduled in job handlers the way all workflows will
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -44,7 +44,6 @@
from galaxy.tools.actions.data_source import DataSourceToolAction
from galaxy.tools.actions.data_manager import DataManagerToolAction
from galaxy.tools.deps import build_dependency_manager
-from galaxy.tools.deps.requirements import parse_requirements_from_xml
from galaxy.tools.parameters import check_param, params_from_strings, params_to_strings
from galaxy.tools.parameters import output_collect
from galaxy.tools.parameters.basic import (BaseURLToolParameter,
@@ -56,7 +55,9 @@
from galaxy.tools.parameters.output import ToolOutputActionGroup
from galaxy.tools.parameters.validation import LateValidationError
from galaxy.tools.filters import FilterFactory
-from galaxy.tools.test import parse_tests_elem
+from galaxy.tools.test import parse_tests
+from galaxy.tools.parser import get_tool_source
+from galaxy.tools.parser.xml import XmlPageSource
from galaxy.util import listify, parse_xml, rst_to_html, string_as_bool, string_to_object, xml_text, xml_to_string
from galaxy.tools.parameters.meta import expand_meta_parameters
from galaxy.util.bunch import Bunch
@@ -742,21 +743,23 @@
def load_tool( self, config_file, guid=None, repository_id=None, **kwds ):
"""Load a single tool from the file named by `config_file` and return an instance of `Tool`."""
# Parse XML configuration file and get the root element
- tree = load_tool( config_file )
- root = tree.getroot()
+ tool_source = get_tool_source( config_file, getattr( self.app.config, "enable_beta_tool_formats", False ) )
# Allow specifying a different tool subclass to instantiate
- if root.find( "type" ) is not None:
- type_elem = root.find( "type" )
- module = type_elem.get( 'module', 'galaxy.tools' )
- cls = type_elem.get( 'class' )
+ tool_module = tool_source.parse_tool_module()
+ if tool_module is not None:
+ module, cls = tool_module
mod = __import__( module, globals(), locals(), [cls] )
ToolClass = getattr( mod, cls )
- elif root.get( 'tool_type', None ) is not None:
- ToolClass = tool_types.get( root.get( 'tool_type' ) )
+ elif tool_source.parse_tool_type():
+ tool_type = tool_source.parse_tool_type()
+ ToolClass = tool_types.get( tool_type )
else:
# Normal tool - only insert dynamic resource parameters for these
# tools.
- if hasattr( self.app, "job_config" ): # toolshed may not have job_config?
+ root = getattr( tool_source, "root", None )
+ # TODO: mucking with the XML directly like this is terrible,
+ # modify inputs directly post load if possible.
+ if root and hasattr( self.app, "job_config" ): # toolshed may not have job_config?
tool_id = root.get( 'id' ) if root else None
parameters = self.app.job_config.get_tool_resource_parameters( tool_id )
if parameters:
@@ -773,7 +776,7 @@
inputs.append( conditional_element )
ToolClass = Tool
- tool = ToolClass( config_file, root, self.app, guid=guid, repository_id=repository_id, **kwds )
+ tool = ToolClass( config_file, tool_source, self.app, guid=guid, repository_id=repository_id, **kwds )
tool_id = tool.id
if not tool_id.startswith("__"):
# do not monitor special tools written to tmp directory - no reason
@@ -1238,7 +1241,7 @@
dict_collection_visible_keys = ( 'id', 'name', 'version', 'description' )
default_template = 'tool_form.mako'
- def __init__( self, config_file, root, app, guid=None, repository_id=None ):
+ def __init__( self, config_file, tool_source, app, guid=None, repository_id=None ):
"""Load a tool from the config named by `config_file`"""
# Determine the full path of the directory where the tool config is
self.config_file = config_file
@@ -1282,7 +1285,7 @@
#populate toolshed repository info, if available
self.populate_tool_shed_info()
# Parse XML element containing configuration
- self.parse( root, guid=guid )
+ self.parse( tool_source, guid=guid )
self.external_runJob_script = app.config.drmaa_external_runjob_script
@property
@@ -1388,47 +1391,55 @@
return section_id, section_name
return None, None
- def parse( self, root, guid=None ):
+ def parse( self, tool_source, guid=None ):
"""
Read tool configuration from the element `root` and fill in `self`.
"""
# Get the (user visible) name of the tool
- self.name = root.get( "name" )
+ self.name = tool_source.parse_name()
if not self.name:
raise Exception( "Missing tool 'name'" )
# Get the UNIQUE id for the tool
- self.old_id = root.get( "id" )
+ self.old_id = tool_source.parse_id()
if guid is None:
self.id = self.old_id
else:
self.id = guid
if not self.id:
raise Exception( "Missing tool 'id'" )
- self.version = root.get( "version" )
+ self.version = tool_source.parse_version()
if not self.version:
# For backward compatibility, some tools may not have versions yet.
self.version = "1.0.0"
+
# Support multi-byte tools
- self.is_multi_byte = string_as_bool( root.get( "is_multi_byte", False ) )
+ self.is_multi_byte = tool_source.parse_is_multi_byte()
# Legacy feature, ignored by UI.
self.force_history_refresh = False
- self.display_interface = string_as_bool( root.get( 'display_interface', str( self.display_interface ) ) )
- self.require_login = string_as_bool( root.get( 'require_login', str( self.require_login ) ) )
- # Load input translator, used by datasource tools to change names/values of incoming parameters
- self.input_translator = root.find( "request_param_translation" )
- if self.input_translator:
- self.input_translator = ToolInputTranslator.from_element( self.input_translator )
+
+ self.display_interface = tool_source.parse_display_interface( default=self.display_interface )
+
+ self.require_login = tool_source.parse_require_login( self.require_login )
+
+ request_param_translation_elem = tool_source.parse_request_param_translation_elem()
+ if request_param_translation_elem is not None:
+ # Load input translator, used by datasource tools to change names/values of incoming parameters
+ self.input_translator = ToolInputTranslator.from_element( request_param_translation_elem )
+ else:
+ self.input_translator = None
+
# Command line (template). Optional for tools that do not invoke a local program
- command = root.find("command")
- if command is not None and command.text is not None:
- self.command = command.text.lstrip() # get rid of leading whitespace
+ command = tool_source.parse_command()
+ if command is not None:
+ self.command = command.lstrip() # get rid of leading whitespace
# Must pre-pend this AFTER processing the cheetah command template
- self.interpreter = command.get( "interpreter", None )
+ self.interpreter = tool_source.parse_interpreter()
else:
self.command = ''
self.interpreter = None
+
# Parameters used to build URL for redirection to external app
- redirect_url_params = root.find( "redirect_url_params" )
+ redirect_url_params = tool_source.parse_redirect_url_params_elem()
if redirect_url_params is not None and redirect_url_params.text is not None:
# get rid of leading / trailing white space
redirect_url_params = redirect_url_params.text.strip()
@@ -1437,25 +1448,26 @@
self.redirect_url_params = redirect_url_params.replace( ' ', '**^**' )
else:
self.redirect_url_params = ''
+
# Short description of the tool
- self.description = xml_text(root, "description")
+ self.description = tool_source.parse_description()
+
# Versioning for tools
self.version_string_cmd = None
- version_cmd = root.find("version_command")
- if version_cmd is not None:
- self.version_string_cmd = version_cmd.text.strip()
- version_cmd_interpreter = version_cmd.get( "interpreter", None )
+ version_command = tool_source.parse_version_command()
+ if version_command is not None:
+ self.version_string_cmd = version_command.strip()
+
+ version_cmd_interpreter = tool_source.parse_version_command_interpreter()
if version_cmd_interpreter:
executable = self.version_string_cmd.split()[0]
abs_executable = os.path.abspath(os.path.join(self.tool_dir, executable))
command_line = self.version_string_cmd.replace(executable, abs_executable, 1)
self.version_string_cmd = version_cmd_interpreter + " " + command_line
+
# Parallelism for tasks, read from tool config.
- parallelism = root.find("parallelism")
- if parallelism is not None and parallelism.get("method"):
- self.parallelism = ParallelismInfo(parallelism)
- else:
- self.parallelism = None
+ self.parallelism = tool_source.parse_parallelism()
+
# Get JobToolConfiguration(s) valid for this particular Tool. At least
# a 'default' will be provided that uses the 'default' handler and
# 'default' destination. I thought about moving this to the
@@ -1468,15 +1480,65 @@
# In the toolshed context, there is no job config.
if 'job_config' in dir(self.app):
self.job_tool_configurations = self.app.job_config.get_job_tool_configurations(self_ids)
+
# Is this a 'hidden' tool (hidden in tool menu)
- self.hidden = xml_text(root, "hidden")
- if self.hidden:
- self.hidden = string_as_bool(self.hidden)
+ self.hidden = tool_source.parse_hidden()
+
+ self.__parse_legacy_features(tool_source)
+
+ # Load any tool specific options (optional)
+ self.options = dict( sanitize=True, refresh=False )
+ self.__update_options_dict( tool_source )
+ self.options = Bunch(** self.options)
+
+ # Parse tool inputs (if there are any required)
+ self.parse_inputs( tool_source )
+
+ # Parse tool help
+ self.parse_help( tool_source )
+
+ # Description of outputs produced by an invocation of the tool
+ self.parse_outputs( tool_source )
+
+ # Parse result handling for tool exit codes and stdout/stderr messages:
+ self.parse_stdio( tool_source )
+ # Any extra generated config files for the tool
+ self.__parse_config_files(tool_source)
+ # Action
+ action = tool_source.parse_action_module()
+ if action is None:
+ self.tool_action = self.default_tool_action()
+ else:
+ module, cls = action
+ mod = __import__( module, globals(), locals(), [cls])
+ self.tool_action = getattr( mod, cls )()
+ # Tests
+ self.__parse_tests(tool_source)
+
+ # Requirements (dependencies)
+ requirements, containers = tool_source.parse_requirements_and_containers()
+ self.requirements = requirements
+ self.containers = containers
+
+ self.citations = self._parse_citations( tool_source )
+
+ # Determine if this tool can be used in workflows
+ self.is_workflow_compatible = self.check_workflow_compatible(tool_source)
+ self.__parse_trackster_conf( tool_source )
+
+ def __parse_legacy_features(self, tool_source):
+ self.code_namespace = dict()
+ self.hook_map = {}
+ self.uihints = {}
+
+ if not hasattr(tool_source, 'root'):
+ return
+
+ # TODO: Move following logic into XmlToolSource.
+ root = tool_source.root
# Load any tool specific code (optional) Edit: INS 5/29/2007,
# allow code files to have access to the individual tool's
# "module" if it has one. Allows us to reuse code files, etc.
- self.code_namespace = dict()
- self.hook_map = {}
for code_elem in root.findall("code"):
for hook_elem in code_elem.findall("hook"):
for key, value in hook_elem.items():
@@ -1485,25 +1547,36 @@
file_name = code_elem.get("file")
code_path = os.path.join( self.tool_dir, file_name )
execfile( code_path, self.code_namespace )
- # Load any tool specific options (optional)
- self.options = dict( sanitize=True, refresh=False )
+
+ # User interface hints
+ uihints_elem = root.find( "uihints" )
+ if uihints_elem is not None:
+ for key, value in uihints_elem.attrib.iteritems():
+ self.uihints[ key ] = value
+
+ def __update_options_dict(self, tool_source):
+ # TODO: Move following logic into ToolSource abstraction.
+ if not hasattr(tool_source, 'root'):
+ return
+
+ root = tool_source.root
for option_elem in root.findall("options"):
for option, value in self.options.copy().items():
if isinstance(value, type(False)):
self.options[option] = string_as_bool(option_elem.get(option, str(value)))
else:
self.options[option] = option_elem.get(option, str(value))
- self.options = Bunch(** self.options)
- # Parse tool inputs (if there are any required)
- self.parse_inputs( root )
- # Parse tool help
- self.parse_help( root )
- # Description of outputs produced by an invocation of the tool
- self.parse_outputs( root )
- # Parse result handling for tool exit codes and stdout/stderr messages:
- self.parse_stdio( root )
- # Any extra generated config files for the tool
+
+ def __parse_tests(self, tool_source):
+ self.__tests_source = tool_source
+ self.__tests_populated = False
+
+ def __parse_config_files(self, tool_source):
self.config_files = []
+ if not hasattr(tool_source, 'root'):
+ return
+
+ root = tool_source.root
conf_parent_elem = root.find("configfiles")
if conf_parent_elem:
for conf_elem in conf_parent_elem.findall( "configfile" ):
@@ -1511,87 +1584,65 @@
filename = conf_elem.get( "filename", None )
text = conf_elem.text
self.config_files.append( ( name, filename, text ) )
- # Action
- action_elem = root.find( "action" )
- if action_elem is None:
- self.tool_action = self.default_tool_action()
- else:
- module = action_elem.get( 'module' )
- cls = action_elem.get( 'class' )
- mod = __import__( module, globals(), locals(), [cls])
- self.tool_action = getattr( mod, cls )()
- # User interface hints
- self.uihints = {}
- uihints_elem = root.find( "uihints" )
- if uihints_elem is not None:
- for key, value in uihints_elem.attrib.iteritems():
- self.uihints[ key ] = value
- # Tests
- self.__tests_elem = root.find( "tests" )
- self.__tests_populated = False
- # Requirements (dependencies)
- requirements, containers = parse_requirements_from_xml( root )
- self.requirements = requirements
- self.containers = containers
+ def __parse_trackster_conf(self, tool_source):
+ self.trackster_conf = None
+ if not hasattr(tool_source, 'root'):
+ return
- self.citations = self._parse_citations( root )
-
- # Determine if this tool can be used in workflows
- self.is_workflow_compatible = self.check_workflow_compatible(root)
# Trackster configuration.
- trackster_conf = root.find( "trackster_conf" )
+ trackster_conf = tool_source.root.find( "trackster_conf" )
if trackster_conf is not None:
self.trackster_conf = TracksterConfig.parse( trackster_conf )
- else:
- self.trackster_conf = None
@property
def tests( self ):
if not self.__tests_populated:
- tests_elem = self.__tests_elem
- if tests_elem:
+ tests_source = self.__tests_source
+ if tests_source:
try:
- self.__tests = parse_tests_elem( self, tests_elem )
+ self.__tests = parse_tests( self, tests_source )
except:
+ self.__tests = None
log.exception( "Failed to parse tool tests" )
else:
self.__tests = None
self.__tests_populated = True
return self.__tests
- def parse_inputs( self, root ):
+ def parse_inputs( self, tool_source ):
"""
Parse the "<inputs>" element and create appropriate `ToolParameter`s.
This implementation supports multiple pages and grouping constructs.
"""
# Load parameters (optional)
- input_elem = root.find("inputs")
+ pages = tool_source.parse_input_pages()
enctypes = set()
- if input_elem is not None:
- # Handle properties of the input form
- self.check_values = string_as_bool( input_elem.get("check_values", self.check_values ) )
- self.nginx_upload = string_as_bool( input_elem.get( "nginx_upload", self.nginx_upload ) )
- self.action = input_elem.get( 'action', self.action )
- # If we have an nginx upload, save the action as a tuple instead of
- # a string. The actual action needs to get url_for run to add any
- # prefixes, and we want to avoid adding the prefix to the
- # nginx_upload_path. This logic is handled in the tool_form.mako
- # template.
- if self.nginx_upload and self.app.config.nginx_upload_path:
- if '?' in urllib.unquote_plus( self.action ):
- raise Exception( 'URL parameters in a non-default tool action can not be used '
- 'in conjunction with nginx upload. Please convert them to '
- 'hidden POST parameters' )
- self.action = (self.app.config.nginx_upload_path + '?nginx_redir=',
- urllib.unquote_plus(self.action))
- self.target = input_elem.get( "target", self.target )
- self.method = input_elem.get( "method", self.method )
- # Parse the actual parameters
- # Handle multiple page case
- pages = input_elem.findall( "page" )
- for page in ( pages or [ input_elem ] ):
- display, inputs = self.parse_input_page( page, enctypes )
+ if pages.inputs_defined:
+ if hasattr(pages, "input_elem"):
+ input_elem = pages.input_elem
+ # Handle properties of the input form
+ self.check_values = string_as_bool( input_elem.get("check_values", self.check_values ) )
+ self.nginx_upload = string_as_bool( input_elem.get( "nginx_upload", self.nginx_upload ) )
+ self.action = input_elem.get( 'action', self.action )
+ # If we have an nginx upload, save the action as a tuple instead of
+ # a string. The actual action needs to get url_for run to add any
+ # prefixes, and we want to avoid adding the prefix to the
+ # nginx_upload_path. This logic is handled in the tool_form.mako
+ # template.
+ if self.nginx_upload and self.app.config.nginx_upload_path:
+ if '?' in urllib.unquote_plus( self.action ):
+ raise Exception( 'URL parameters in a non-default tool action can not be used '
+ 'in conjunction with nginx upload. Please convert them to '
+ 'hidden POST parameters' )
+ self.action = (self.app.config.nginx_upload_path + '?nginx_redir=',
+ urllib.unquote_plus(self.action))
+ self.target = input_elem.get( "target", self.target )
+ self.method = input_elem.get( "method", self.method )
+ # Parse the actual parameters
+ # Handle multiple page case
+ for page_source in pages.page_sources:
+ display, inputs = self.parse_input_page( page_source, enctypes )
self.inputs_by_page.append( inputs )
self.inputs.update( inputs )
self.display_by_page.append( display )
@@ -1613,24 +1664,28 @@
# thus hardcoded) FIXME: hidden parameters aren't
# parameters at all really, and should be passed in a different
# way, making this check easier.
- self.template_macro_params = template_macro_params(root)
+ template_macros = {}
+ if hasattr(tool_source, 'root'):
+ template_macros = template_macro_params(tool_source.root)
+ self.template_macro_params = template_macros
for param in self.inputs.values():
if not isinstance( param, ( HiddenToolParameter, BaseURLToolParameter ) ):
self.input_required = True
break
- def parse_help( self, root ):
+ def parse_help( self, tool_source ):
"""
Parse the help text for the tool. Formatted in reStructuredText, but
stored as Mako to allow for dynamic image paths.
This implementation supports multiple pages.
"""
# TODO: Allow raw HTML or an external link.
- self.help = root.find("help")
+ self.help = None
self.help_by_page = list()
help_header = ""
help_footer = ""
- if self.help is not None:
+ if hasattr( tool_source, 'root' ) and tool_source.root.find( 'help' ) is not None:
+ self.help = tool_source.root.find( 'help' )
if self.repository_id and self.help.text.find( '.. image:: ' ) >= 0:
# Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy.
lock = threading.Lock()
@@ -1667,203 +1722,32 @@
while len( self.help_by_page ) < self.npages:
self.help_by_page.append( self.help )
- def parse_outputs( self, root ):
+ def parse_outputs( self, tool_source ):
"""
Parse <outputs> elements and fill in self.outputs (keyed by name)
"""
self.outputs = odict()
- out_elem = root.find("outputs")
- if not out_elem:
- return
- for data_elem in out_elem.findall("data"):
- output = ToolOutput( data_elem.get("name") )
- output.format = data_elem.get("format", "data")
- output.change_format = data_elem.findall("change_format")
- output.format_source = data_elem.get("format_source", None)
- output.metadata_source = data_elem.get("metadata_source", "")
- output.parent = data_elem.get("parent", None)
- output.label = xml_text( data_elem, "label" )
- output.count = int( data_elem.get("count", 1) )
- output.filters = data_elem.findall( 'filter' )
- output.from_work_dir = data_elem.get("from_work_dir", None)
- output.hidden = string_as_bool( data_elem.get("hidden", "") )
- output.tool = self
- output.actions = ToolOutputActionGroup( output, data_elem.find( 'actions' ) )
- output.dataset_collectors = output_collect.dataset_collectors_from_elem( data_elem )
+ for output in tool_source.parse_outputs(self):
self.outputs[ output.name ] = output
# TODO: Include the tool's name in any parsing warnings.
- def parse_stdio( self, root ):
+ def parse_stdio( self, tool_source ):
"""
Parse <stdio> element(s) and fill in self.return_codes,
self.stderr_rules, and self.stdout_rules. Return codes have a range
and an error type (fault or warning). Stderr and stdout rules have
a regular expression and an error level (fault or warning).
"""
- try:
- self.stdio_exit_codes = list()
- self.stdio_regexes = list()
+ exit_codes, regexes = tool_source.parse_stdio()
+ self.stdio_exit_codes = exit_codes
+ self.stdio_regexes = regexes
- # We should have a single <stdio> element, but handle the case for
- # multiples.
- # For every stdio element, add all of the exit_code and regex
- # subelements that we find:
- for stdio_elem in ( root.findall( 'stdio' ) ):
- self.parse_stdio_exit_codes( stdio_elem )
- self.parse_stdio_regexes( stdio_elem )
- except Exception:
- log.error( "Exception in parse_stdio! " + str(sys.exc_info()) )
+ def _parse_citations( self, tool_source ):
+ # TODO: Move following logic into ToolSource abstraction.
+ if not hasattr(tool_source, 'root'):
+ return []
- def parse_stdio_exit_codes( self, stdio_elem ):
- """
- Parse the tool's <stdio> element's <exit_code> subelements.
- This will add all of those elements, if any, to self.stdio_exit_codes.
- """
- try:
- # Look for all <exit_code> elements. Each exit_code element must
- # have a range/value.
- # Exit-code ranges have precedence over a single exit code.
- # So if there are value and range attributes, we use the range
- # attribute. If there is neither a range nor a value, then print
- # a warning and skip to the next.
- for exit_code_elem in ( stdio_elem.findall( "exit_code" ) ):
- exit_code = ToolStdioExitCode()
- # Each exit code has an optional description that can be
- # part of the "desc" or "description" attributes:
- exit_code.desc = exit_code_elem.get( "desc" )
- if None == exit_code.desc:
- exit_code.desc = exit_code_elem.get( "description" )
- # Parse the error level:
- exit_code.error_level = (
- self.parse_error_level( exit_code_elem.get( "level" )))
- code_range = exit_code_elem.get( "range", "" )
- if None == code_range:
- code_range = exit_code_elem.get( "value", "" )
- if None == code_range:
- log.warning( "Tool stdio exit codes must have "
- + "a range or value" )
- continue
- # Parse the range. We look for:
- # :Y
- # X:
- # X:Y - Split on the colon. We do not allow a colon
- # without a beginning or end, though we could.
- # Also note that whitespace is eliminated.
- # TODO: Turn this into a single match - it should be
- # more efficient.
- code_range = re.sub( "\s", "", code_range )
- code_ranges = re.split( ":", code_range )
- if ( len( code_ranges ) == 2 ):
- if ( None == code_ranges[0] or '' == code_ranges[0] ):
- exit_code.range_start = float( "-inf" )
- else:
- exit_code.range_start = int( code_ranges[0] )
- if ( None == code_ranges[1] or '' == code_ranges[1] ):
- exit_code.range_end = float( "inf" )
- else:
- exit_code.range_end = int( code_ranges[1] )
- # If we got more than one colon, then ignore the exit code.
- elif ( len( code_ranges ) > 2 ):
- log.warning( "Invalid tool exit_code range %s - ignored"
- % code_range )
- continue
- # Else we have a singular value. If it's not an integer, then
- # we'll just write a log message and skip this exit_code.
- else:
- try:
- exit_code.range_start = int( code_range )
- except:
- log.error( code_range )
- log.warning( "Invalid range start for tool's exit_code %s: exit_code ignored" % code_range )
- continue
- exit_code.range_end = exit_code.range_start
- # TODO: Check if we got ">", ">=", "<", or "<=":
- # Check that the range, regardless of how we got it,
- # isn't bogus. If we have two infinite values, then
- # the start must be -inf and the end must be +inf.
- # So at least warn about this situation:
- if ( isinf( exit_code.range_start ) and
- isinf( exit_code.range_end ) ):
- log.warning( "Tool exit_code range %s will match on "
- + "all exit codes" % code_range )
- self.stdio_exit_codes.append( exit_code )
- except Exception:
- log.error( "Exception in parse_stdio_exit_codes! "
- + str(sys.exc_info()) )
- trace = sys.exc_info()[2]
- if ( None != trace ):
- trace_msg = repr( traceback.format_tb( trace ) )
- log.error( "Traceback: %s" % trace_msg )
-
- def parse_stdio_regexes( self, stdio_elem ):
- """
- Look in the tool's <stdio> elem for all <regex> subelements
- that define how to look for warnings and fatal errors in
- stdout and stderr. This will add all such regex elements
- to the Tols's stdio_regexes list.
- """
- try:
- # Look for every <regex> subelement. The regular expression
- # will have "match" and "source" (or "src") attributes.
- for regex_elem in ( stdio_elem.findall( "regex" ) ):
- # TODO: Fill in ToolStdioRegex
- regex = ToolStdioRegex()
- # Each regex has an optional description that can be
- # part of the "desc" or "description" attributes:
- regex.desc = regex_elem.get( "desc" )
- if None == regex.desc:
- regex.desc = regex_elem.get( "description" )
- # Parse the error level
- regex.error_level = (
- self.parse_error_level( regex_elem.get( "level" ) ) )
- regex.match = regex_elem.get( "match", "" )
- if None == regex.match:
- # TODO: Convert the offending XML element to a string
- log.warning( "Ignoring tool's stdio regex element %s - "
- "the 'match' attribute must exist" )
- continue
- # Parse the output sources. We look for the "src", "source",
- # and "sources" attributes, in that order. If there is no
- # such source, then the source defaults to stderr & stdout.
- # Look for a comma and then look for "err", "error", "out",
- # and "output":
- output_srcs = regex_elem.get( "src" )
- if None == output_srcs:
- output_srcs = regex_elem.get( "source" )
- if None == output_srcs:
- output_srcs = regex_elem.get( "sources" )
- if None == output_srcs:
- output_srcs = "output,error"
- output_srcs = re.sub( "\s", "", output_srcs )
- src_list = re.split( ",", output_srcs )
- # Just put together anything to do with "out", including
- # "stdout", "output", etc. Repeat for "stderr", "error",
- # and anything to do with "err". If neither stdout nor
- # stderr were specified, then raise a warning and scan both.
- for src in src_list:
- if re.search( "both", src, re.IGNORECASE ):
- regex.stdout_match = True
- regex.stderr_match = True
- if re.search( "out", src, re.IGNORECASE ):
- regex.stdout_match = True
- if re.search( "err", src, re.IGNORECASE ):
- regex.stderr_match = True
- if (not regex.stdout_match and not regex.stderr_match):
- log.warning( "Tool id %s: unable to determine if tool "
- "stream source scanning is output, error, "
- "or both. Defaulting to use both." % self.id )
- regex.stdout_match = True
- regex.stderr_match = True
- self.stdio_regexes.append( regex )
- except Exception:
- log.error( "Exception in parse_stdio_exit_codes! "
- + str(sys.exc_info()) )
- trace = sys.exc_info()[2]
- if ( None != trace ):
- trace_msg = repr( traceback.format_tb( trace ) )
- log.error( "Traceback: %s" % trace_msg )
-
- def _parse_citations( self, root ):
+ root = tool_source.root
citations = []
citations_elem = root.find("citations")
if not citations_elem:
@@ -1877,49 +1761,18 @@
citations.append( citation )
return citations
- # TODO: This method doesn't have to be part of the Tool class.
- def parse_error_level( self, err_level ):
- """
- Parses error level and returns error level enumeration. If
- unparsable, returns 'fatal'
- """
- return_level = StdioErrorLevel.FATAL
- try:
- if err_level:
- if ( re.search( "log", err_level, re.IGNORECASE ) ):
- return_level = StdioErrorLevel.LOG
- elif ( re.search( "warning", err_level, re.IGNORECASE ) ):
- return_level = StdioErrorLevel.WARNING
- elif ( re.search( "fatal", err_level, re.IGNORECASE ) ):
- return_level = StdioErrorLevel.FATAL
- else:
- log.debug( "Tool %s: error level %s did not match log/warning/fatal" %
- ( self.id, err_level ) )
- except Exception:
- log.error( "Exception in parse_error_level "
- + str(sys.exc_info() ) )
- trace = sys.exc_info()[2]
- if ( None != trace ):
- trace_msg = repr( traceback.format_tb( trace ) )
- log.error( "Traceback: %s" % trace_msg )
- return return_level
-
- def parse_input_page( self, input_elem, enctypes ):
+ def parse_input_page( self, page_source, enctypes ):
"""
Parse a page of inputs. This basically just calls 'parse_input_elem',
but it also deals with possible 'display' elements which are supported
only at the top/page level (not in groups).
"""
- inputs = self.parse_input_elem( input_elem, enctypes )
+ inputs = self.parse_input_elem( page_source, enctypes )
# Display
- display_elem = input_elem.find("display")
- if display_elem is not None:
- display = xml_to_string(display_elem)
- else:
- display = None
+ display = page_source.parse_display()
return display, inputs
- def parse_input_elem( self, parent_elem, enctypes, context=None ):
+ def parse_input_elem( self, page_source, enctypes, context=None ):
"""
Parse a parent element whose children are inputs -- these could be
groups (repeat, conditional) or param elements. Groups will be parsed
@@ -1927,29 +1780,31 @@
"""
rval = odict()
context = ExpressionContext( rval, context )
- for elem in parent_elem:
+ for input_source in page_source.parse_input_sources():
# Repeat group
- if elem.tag == "repeat":
+ input_type = input_source.parse_input_type()
+ if input_type == "repeat":
group = Repeat()
- group.name = elem.get( "name" )
- group.title = elem.get( "title" )
- group.help = elem.get( "help", None )
- group.inputs = self.parse_input_elem( elem, enctypes, context )
- group.default = int( elem.get( "default", 0 ) )
- group.min = int( elem.get( "min", 0 ) )
+ group.name = input_source.get( "name" )
+ group.title = input_source.get( "title" )
+ group.help = input_source.get( "help", None )
+ page_source = input_source.parse_nested_inputs_source()
+ group.inputs = self.parse_input_elem( page_source, enctypes, context )
+ group.default = int( input_source.get( "default", 0 ) )
+ group.min = int( input_source.get( "min", 0 ) )
# Use float instead of int so that 'inf' can be used for no max
- group.max = float( elem.get( "max", "inf" ) )
+ group.max = float( input_source.get( "max", "inf" ) )
assert group.min <= group.max, \
ValueError( "Min repeat count must be less-than-or-equal to the max." )
# Force default to be within min-max range
group.default = min( max( group.default, group.min ), group.max )
rval[group.name] = group
- elif elem.tag == "conditional":
+ elif input_type == "conditional":
group = Conditional()
- group.name = elem.get( "name" )
- group.value_ref = elem.get( 'value_ref', None )
- group.value_ref_in_group = string_as_bool( elem.get( 'value_ref_in_group', 'True' ) )
- value_from = elem.get( "value_from" )
+ group.name = input_source.get( "name" )
+ group.value_ref = input_source.get( 'value_ref', None )
+ group.value_ref_in_group = input_source.get_bool( 'value_ref_in_group', True )
+ value_from = input_source.get("value_from", None)
if value_from:
value_from = value_from.split( ':' )
group.value_from = locals().get( value_from[0] )
@@ -1961,24 +1816,23 @@
case = ConditionalWhen()
case.value = case_value
if case_inputs:
- case.inputs = self.parse_input_elem(
- ElementTree.XML( "<when>%s</when>" % case_inputs ), enctypes, context )
+ page_source = XmlPageSource( ElementTree.XML( "<when>%s</when>" % case_inputs ) )
+ case.inputs = self.parse_input_elem( page_source, enctypes, context )
else:
case.inputs = odict()
group.cases.append( case )
else:
# Should have one child "input" which determines the case
- input_elem = elem.find( "param" )
- assert input_elem is not None, "<conditional> must have a child <param>"
- group.test_param = self.parse_param_elem( input_elem, enctypes, context )
+ test_param_input_source = input_source.parse_test_input_source()
+ group.test_param = self.parse_param_elem( test_param_input_source, enctypes, context )
possible_cases = list( group.test_param.legal_values ) # store possible cases, undefined whens will have no inputs
# Must refresh when test_param changes
group.test_param.refresh_on_change = True
# And a set of possible cases
- for case_elem in elem.findall( "when" ):
+ for (value, case_inputs_source) in input_source.parse_when_input_sources():
case = ConditionalWhen()
- case.value = case_elem.get( "value" )
- case.inputs = self.parse_input_elem( case_elem, enctypes, context )
+ case.value = value
+ case.inputs = self.parse_input_elem( case_inputs_source, enctypes, context )
group.cases.append( case )
try:
possible_cases.remove( case.value )
@@ -1993,7 +1847,8 @@
case.inputs = odict()
group.cases.append( case )
rval[group.name] = group
- elif elem.tag == "upload_dataset":
+ elif input_type == "upload_dataset":
+ elem = input_source.elem()
group = UploadDataset()
group.name = elem.get( "name" )
group.title = elem.get( "title" )
@@ -2003,23 +1858,24 @@
rval[ group.file_type_name ].refresh_on_change = True
rval[ group.file_type_name ].refresh_on_change_values = \
self.app.datatypes_registry.get_composite_extensions()
- group.inputs = self.parse_input_elem( elem, enctypes, context )
+ group_page_source = XmlPageSource(elem)
+ group.inputs = self.parse_input_elem( group_page_source, enctypes, context )
rval[ group.name ] = group
- elif elem.tag == "param":
- param = self.parse_param_elem( elem, enctypes, context )
+ elif input_type == "param":
+ param = self.parse_param_elem( input_source, enctypes, context )
rval[param.name] = param
if hasattr( param, 'data_ref' ):
param.ref_input = context[ param.data_ref ]
self.input_params.append( param )
return rval
- def parse_param_elem( self, input_elem, enctypes, context ):
+ def parse_param_elem( self, input_source, enctypes, context ):
"""
Parse a single "<param>" element and return a ToolParameter instance.
Also, if the parameter has a 'required_enctype' add it to the set
enctypes.
"""
- param = ToolParameter.build( self, input_elem )
+ param = ToolParameter.build( self, input_source )
param_enctype = param.get_required_enctype()
if param_enctype:
enctypes.add( param_enctype )
@@ -2039,7 +1895,7 @@
self.repository_owner = tool_shed_repository.owner
self.installed_changeset_revision = tool_shed_repository.installed_changeset_revision
- def check_workflow_compatible( self, root ):
+ def check_workflow_compatible( self, tool_source ):
"""
Determine if a tool can be used in workflows. External tools and the
upload tool are currently not supported by workflows.
@@ -2052,8 +1908,11 @@
# right now
if self.tool_type.startswith( 'data_source' ):
return False
- if not string_as_bool( root.get( "workflow_compatible", "True" ) ):
- return False
+
+ if hasattr( tool_source, "root"):
+ root = tool_source.root
+ if not string_as_bool( root.get( "workflow_compatible", "True" ) ):
+ return False
# TODO: Anyway to capture tools that dynamically change their own
# outputs?
return True
@@ -3263,8 +3122,8 @@
def _build_GALAXY_URL_parameter( self ):
return ToolParameter.build( self, ElementTree.XML( '<param name="GALAXY_URL" type="baseurl" value="/tool_runner?tool_id=%s" />' % self.id ) )
- def parse_inputs( self, root ):
- super( DataSourceTool, self ).parse_inputs( root )
+ def parse_inputs( self, tool_source ):
+ super( DataSourceTool, self ).parse_inputs( tool_source )
if 'GALAXY_URL' not in self.inputs:
self.inputs[ 'GALAXY_URL' ] = self._build_GALAXY_URL_parameter()
self.inputs_by_page[0][ 'GALAXY_URL' ] = self.inputs[ 'GALAXY_URL' ]
@@ -3503,6 +3362,33 @@
self.desc = ""
+class TestCollectionDef( object ):
+
+ def __init__( self, elem, parse_param_elem ):
+ self.elements = []
+ attrib = dict( elem.attrib )
+ self.collection_type = attrib[ "type" ]
+ self.name = attrib.get( "name", "Unnamed Collection" )
+ for element in elem.findall( "element" ):
+ element_attrib = dict( element.attrib )
+ element_identifier = element_attrib[ "name" ]
+ nested_collection_elem = element.find( "collection" )
+ if nested_collection_elem:
+ self.elements.append( ( element_identifier, TestCollectionDef( nested_collection_elem, parse_param_elem ) ) )
+ else:
+ self.elements.append( ( element_identifier, parse_param_elem( element ) ) )
+
+ def collect_inputs( self ):
+ inputs = []
+ for element in self.elements:
+ value = element[ 1 ]
+ if isinstance( value, TestCollectionDef ):
+ inputs.extend( value.collect_inputs() )
+ else:
+ inputs.append( value )
+ return inputs
+
+
def json_fix( val ):
if isinstance( val, list ):
return [ json_fix( v ) for v in val ]
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/deps/requirements.py
--- a/lib/galaxy/tools/deps/requirements.py
+++ b/lib/galaxy/tools/deps/requirements.py
@@ -45,6 +45,12 @@
return ContainerDescription( identifier=identifier, type=type )
+def parse_requirements_from_dict( root_dict ):
+ requirements = root_dict.get("requirements", [])
+ containers = root_dict.get("containers", [])
+ return map(ToolRequirement.from_dict, requirements), map(ContainerDescription.from_dict, containers)
+
+
def parse_requirements_from_xml( xml_root ):
"""
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/imp_exp/__init__.py
--- a/lib/galaxy/tools/imp_exp/__init__.py
+++ b/lib/galaxy/tools/imp_exp/__init__.py
@@ -13,12 +13,7 @@
log = logging.getLogger(__name__)
-
-def load_history_imp_exp_tools( toolbox ):
- """ Adds tools for importing/exporting histories to archives. """
- # Use same process as that used in load_external_metadata_tool; see that
- # method for why create tool description files on the fly.
- tool_xml_text = """
+EXPORT_HISTORY_TEXT = """
<tool id="__EXPORT_HISTORY__" name="Export History" version="0.1" tool_type="export_history"><type class="ExportHistoryTool" module="galaxy.tools"/><action module="galaxy.tools.actions.history_imp_exp" class="ExportHistoryToolAction"/>
@@ -32,7 +27,14 @@
<data format="gzip" name="output_file"/></outputs></tool>
- """
+"""
+
+
+def load_history_imp_exp_tools( toolbox ):
+ """ Adds tools for importing/exporting histories to archives. """
+ # Use same process as that used in load_external_metadata_tool; see that
+ # method for why create tool description files on the fly.
+ tool_xml_text = EXPORT_HISTORY_TEXT
# Load export tool.
tmp_name = tempfile.NamedTemporaryFile()
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -18,6 +18,8 @@
from sanitize import ToolParameterSanitizer
import validation
import dynamic_options
+import galaxy.tools.parser
+from ..parser import get_input_source as ensure_input_source
from ..parameters import history_query
from .dataset_matcher import DatasetMatcher
from .dataset_matcher import DatasetCollectionMatcher
@@ -37,24 +39,34 @@
"""
dict_collection_visible_keys = ( 'name', 'type', 'label', 'help' )
- def __init__( self, tool, param, context=None ):
+ def __init__( self, tool, input_source, context=None ):
+ input_source = ensure_input_source(input_source)
self.tool = tool
self.refresh_on_change = False
self.refresh_on_change_values = []
- self.name = param.get("name")
- self.type = param.get("type")
- self.label = util.xml_text(param, "label")
- self.help = util.xml_text(param, "help")
- self.sanitizer = param.find( "sanitizer" )
- if self.sanitizer is not None:
- self.sanitizer = ToolParameterSanitizer.from_element( self.sanitizer )
+ self.name = input_source.get("name")
+ self.type = input_source.get("type")
+ self.label = input_source.parse_label()
+ self.help = input_source.parse_help()
+ sanitizer_elem = input_source.parse_sanitizer_elem()
+ if sanitizer_elem:
+ self.sanitizer = ToolParameterSanitizer.from_element( sanitizer_elem )
+ else:
+ self.sanitizer = None
self.html = "no html set"
- self.repeat = param.get("repeat", None)
- self.condition = param.get( "condition", None )
+ try:
+ # These don't do anything right? These we should
+ # delete these two lines and eliminate checks for
+ # self.repeat in this file. -John
+ self.repeat = input_source.elem().get("repeat", None)
+ self.condition = input_source.elem().get( "condition", None )
+ except Exception:
+ self.repeat = None
+
# Optional DataToolParameters are used in tools like GMAJ and LAJ
- self.optional = string_as_bool( param.get( 'optional', False ) )
+ self.optional = input_source.parse_optional()
self.validators = []
- for elem in param.findall("validator"):
+ for elem in input_source.parse_validator_elems():
self.validators.append( validation.Validator.from_element( self, elem ) )
@property
@@ -232,11 +244,12 @@
>>> print p.get_html( value="meh" )
<input type="text" name="blah" size="4" value="meh">
"""
- def __init__( self, tool, elem ):
- ToolParameter.__init__( self, tool, elem )
- self.size = elem.get( 'size' )
- self.value = elem.get( 'value' )
- self.area = string_as_bool( elem.get( 'area', False ) )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source(input_source)
+ ToolParameter.__init__( self, tool, input_source )
+ self.size = input_source.get( 'size' )
+ self.value = input_source.get( 'value' )
+ self.area = input_source.get_bool( 'area', False )
def get_html_field( self, trans=None, value=None, other_values={} ):
if value is None:
@@ -274,8 +287,9 @@
dict_collection_visible_keys = ToolParameter.dict_collection_visible_keys + ( 'min', 'max' )
- def __init__( self, tool, elem ):
- TextToolParameter.__init__( self, tool, elem )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source(input_source)
+ TextToolParameter.__init__( self, tool, input_source )
if self.value:
try:
int( self.value )
@@ -283,8 +297,8 @@
raise ValueError( "An integer is required" )
elif self.value is None and not self.optional:
raise ValueError( "The settings for the field named '%s' require a 'value' setting and optionally a default value which must be an integer" % self.name )
- self.min = elem.get( 'min' )
- self.max = elem.get( 'max' )
+ self.min = input_source.get( 'min' )
+ self.max = input_source.get( 'max' )
if self.min:
try:
self.min = int( self.min )
@@ -352,10 +366,11 @@
dict_collection_visible_keys = ToolParameter.dict_collection_visible_keys + ( 'min', 'max' )
- def __init__( self, tool, elem ):
- TextToolParameter.__init__( self, tool, elem )
- self.min = elem.get( 'min' )
- self.max = elem.get( 'max' )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source(input_source)
+ TextToolParameter.__init__( self, tool, input_source )
+ self.min = input_source.get( 'min' )
+ self.max = input_source.get( 'max' )
if self.value:
try:
float( self.value )
@@ -429,11 +444,12 @@
>>> print p.to_param_dict_string( False )
cellophane chests
"""
- def __init__( self, tool, elem ):
- ToolParameter.__init__( self, tool, elem )
- self.truevalue = elem.get( 'truevalue', 'true' )
- self.falsevalue = elem.get( 'falsevalue', 'false' )
- self.checked = string_as_bool( elem.get( 'checked' ) )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source(input_source)
+ ToolParameter.__init__( self, tool, input_source )
+ self.truevalue = input_source.get( 'truevalue', 'true' )
+ self.falsevalue = input_source.get( 'falsevalue', 'false' )
+ self.checked = input_source.get_bool( 'checked', False )
def get_html_field( self, trans=None, value=None, other_values={} ):
checked = self.checked
@@ -490,12 +506,13 @@
>>> print p.get_html()
<input type="file" name="blah" galaxy-ajax-upload="true">
"""
- def __init__( self, tool, elem ):
+ def __init__( self, tool, input_source ):
"""
Example: C{<param name="bins" type="file" />}
"""
- ToolParameter.__init__( self, tool, elem )
- self.ajax = string_as_bool( elem.get( 'ajax-upload' ) )
+ input_source = ensure_input_source(input_source)
+ ToolParameter.__init__( self, tool, input_source )
+ self.ajax = input_source.get_bool( 'ajax-upload', False )
def get_html_field( self, trans=None, value=None, other_values={} ):
return form_builder.FileField( self.name, ajax=self.ajax, value=value )
@@ -553,11 +570,12 @@
"""
Parameter that takes a file uploaded via FTP as a value.
"""
- def __init__( self, tool, elem ):
+ def __init__( self, tool, input_source ):
"""
Example: C{<param name="bins" type="file" />}
"""
- ToolParameter.__init__( self, tool, elem )
+ input_source = ensure_input_source(input_source)
+ ToolParameter.__init__( self, tool, input_source )
@property
def visible( self ):
@@ -608,9 +626,10 @@
>>> print p.get_html()
<input type="hidden" name="blah" value="wax so rockin">
"""
- def __init__( self, tool, elem ):
- ToolParameter.__init__( self, tool, elem )
- self.value = elem.get( 'value' )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source( input_source )
+ ToolParameter.__init__( self, tool, input_source )
+ self.value = input_source.get( 'value' )
def get_html_field( self, trans=None, value=None, other_values={} ):
return form_builder.HiddenField( self.name, self.value )
@@ -632,9 +651,10 @@
current server base url. Used in all redirects.
"""
- def __init__( self, tool, elem ):
- ToolParameter.__init__( self, tool, elem )
- self.value = elem.get( 'value', '' )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source( input_source )
+ ToolParameter.__init__( self, tool, input_source )
+ self.value = input_source.get( 'value', '' )
def get_value( self, trans ):
# url = trans.request.base + self.value
@@ -737,30 +757,25 @@
>>> print p.to_param_dict_string( ["y", "z"] )
y,z
"""
- def __init__( self, tool, elem, context=None ):
- ToolParameter.__init__( self, tool, elem )
- self.multiple = string_as_bool( elem.get( 'multiple', False ) )
+ def __init__( self, tool, input_source, context=None ):
+ input_source = ensure_input_source( input_source )
+ ToolParameter.__init__( self, tool, input_source )
+ self.multiple = input_source.get_bool( 'multiple', False )
# Multiple selects are optional by default, single selection is the inverse.
- self.optional = string_as_bool( elem.get( 'optional', self.multiple ) )
- self.display = elem.get( 'display', None )
- self.separator = elem.get( 'separator', ',' )
+ self.optional = input_source.parse_optional( self.multiple )
+ self.display = input_source.get( 'display', None )
+ self.separator = input_source.get( 'separator', ',' )
self.legal_values = set()
# TODO: the <dynamic_options> tag is deprecated and should be replaced with the <options> tag.
- self.dynamic_options = elem.get( "dynamic_options", None )
- options = elem.find( 'options' )
- if options is None:
- self.options = None
- else:
- self.options = dynamic_options.DynamicOptions( options, self )
+ self.dynamic_options = input_source.get( "dynamic_options", None )
+ self.options = input_source.parse_dynamic_options(self)
+ if self.options is not None:
for validator in self.options.validators:
self.validators.append( validator )
if self.dynamic_options is None and self.options is None:
- self.static_options = list()
- for index, option in enumerate( elem.findall( "option" ) ):
- value = option.get( "value" )
+ self.static_options = input_source.parse_static_options()
+ for (title, value, selected) in self.static_options:
self.legal_values.add( value )
- selected = string_as_bool( option.get( "selected", False ) )
- self.static_options.append( ( option.text or value, value, selected ) )
self.is_dynamic = ( ( self.dynamic_options is not None ) or ( self.options is not None ) )
def get_options( self, trans, other_values ):
@@ -1133,29 +1148,24 @@
>>> print clp.name
numerical_column
"""
- def __init__( self, tool, elem ):
- SelectToolParameter.__init__( self, tool, elem )
+ def __init__( self, tool, input_source ):
+ input_source = ensure_input_source( input_source )
+ SelectToolParameter.__init__( self, tool, input_source )
self.tool = tool
- self.numerical = string_as_bool( elem.get( "numerical", False ))
- # Allow specifing force_select for backward compat., but probably
- # should use optional going forward for consistency with other
- # parameters.
- if "force_select" in elem.attrib:
- self.force_select = string_as_bool( elem.get( "force_select" ) )
- else:
- self.force_select = not string_as_bool( elem.get( "optional", False ) )
- self.accept_default = string_as_bool( elem.get( "accept_default", False ))
- self.data_ref = elem.get( "data_ref", None )
+ self.numerical = input_source.get_bool( "numerical", False )
+ self.force_select = not input_source.parse_optional( False )
+ self.accept_default = input_source.get_bool( "accept_default", False )
+ self.data_ref = input_source.get( "data_ref", None )
self.ref_input = None
# Legacy style default value specification...
- self.default_value = elem.get( "default_value", None )
+ self.default_value = input_source.get( "default_value", None )
if self.default_value is None:
# Newer style... more in line with other parameters.
- self.default_value = elem.get( "value", None )
+ self.default_value = input_source.get( "value", None )
if self.default_value is not None:
self.default_value = ColumnListParameter._strip_c( self.default_value )
self.is_dynamic = True
- self.usecolnames = string_as_bool( elem.get( "use_header_names", False ))
+ self.usecolnames = input_source.get_bool( "use_header_names", False )
def from_html( self, value, trans=None, context={} ):
"""
@@ -1413,13 +1423,18 @@
>>> print p.options
[{'selected': False, 'name': 'Heading 1', 'value': 'heading1', 'options': [{'selected': False, 'name': 'Option 1', 'value': 'option1', 'options': []}, {'selected': False, 'name': 'Option 2', 'value': 'option2', 'options': []}, {'selected': False, 'name': 'Heading 1', 'value': 'heading1', 'options': [{'selected': False, 'name': 'Option 3', 'value': 'option3', 'options': []}, {'selected': False, 'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'selected': False, 'name': 'Option 5', 'value': 'option5', 'options': []}]
"""
- def __init__( self, tool, elem, context=None ):
+ def __init__( self, tool, input_source, context=None ):
+ input_source = ensure_input_source( input_source )
+
def recurse_option_elems( cur_options, option_elems ):
for option_elem in option_elems:
selected = string_as_bool( option_elem.get( 'selected', False ) )
cur_options.append( { 'name': option_elem.get( 'name' ), 'value': option_elem.get( 'value' ), 'options': [], 'selected': selected } )
recurse_option_elems( cur_options[-1]['options'], option_elem.findall( 'option' ) )
- ToolParameter.__init__( self, tool, elem )
+ ToolParameter.__init__( self, tool, input_source )
+ # TODO: abstract XML out of here - so non-XML InputSources can
+ # specify DrillDown parameters.
+ elem = input_source.elem()
self.multiple = string_as_bool( elem.get( 'multiple', False ) )
self.display = elem.get( 'display', None )
self.hierarchy = elem.get( 'hierarchy', 'exact' ) # exact or recurse
@@ -1653,8 +1668,8 @@
class BaseDataToolParameter( ToolParameter ):
- def __init__( self, tool, elem, trans ):
- super(BaseDataToolParameter, self).__init__( tool, elem )
+ def __init__( self, tool, input_source, trans ):
+ super(BaseDataToolParameter, self).__init__( tool, input_source )
def _get_history( self, trans, history=None ):
class_name = self.__class__.__name__
@@ -1693,32 +1708,27 @@
datatypes_registry = tool.app.datatypes_registry
return datatypes_registry
- def _parse_formats( self, trans, tool, elem ):
+ def _parse_formats( self, trans, tool, input_source ):
datatypes_registry = self._datatypes_registery( trans, tool )
# Build tuple of classes for supported data formats
formats = []
- self.extensions = elem.get( 'format', 'data' ).split( "," )
+ self.extensions = input_source.get( 'format', 'data' ).split( "," )
normalized_extensions = [extension.strip().lower() for extension in self.extensions]
for extension in normalized_extensions:
formats.append( datatypes_registry.get_datatype_by_extension( extension ) )
self.formats = formats
- def _parse_options( self, elem ):
+ def _parse_options( self, input_source ):
# TODO: Enhance dynamic options for DataToolParameters. Currently,
# only the special case key='build' of type='data_meta' is
# a valid filter
- options = elem.find( 'options' )
- if options is None:
- self.options = None
- self.options_filter_attribute = None
- else:
- self.options = dynamic_options.DynamicOptions( options, self )
-
- #HACK to get around current hardcoded limitation of when a set of dynamic options is defined for a DataToolParameter
- #it always causes available datasets to be filtered by dbkey
- #this behavior needs to be entirely reworked (in a backwards compatible manner)
- self.options_filter_attribute = options.get( 'options_filter_attribute', None )
+ self.options_filter_attribute = None
+ self.options = input_source.parse_dynamic_options( self )
+ if self.options:
+ # TODO: Abstract away XML handling here.
+ options_elem = input_source.elem().find('options')
+ self.options_filter_attribute = options_elem.get( 'options_filter_attribute', None )
self.is_dynamic = self.options is not None
def _switch_fields( self, fields, default_field ):
@@ -1743,21 +1753,19 @@
security stuff will dramatically alter this anyway.
"""
- def __init__( self, tool, elem, trans=None):
- super(DataToolParameter, self).__init__( tool, elem, trans )
+ def __init__( self, tool, input_source, trans=None):
+ input_source = ensure_input_source( input_source )
+ super(DataToolParameter, self).__init__( tool, input_source, trans )
# Add metadata validator
- if not string_as_bool( elem.get( 'no_validation', False ) ):
+ if not input_source.get_bool( 'no_validation', False ):
self.validators.append( validation.MetadataValidator() )
- self._parse_formats( trans, tool, elem )
- self.multiple = string_as_bool( elem.get( 'multiple', False ) )
- self._parse_options( elem )
+ self._parse_formats( trans, tool, input_source )
+ self.multiple = input_source.get_bool('multiple', False)
+ self._parse_options( input_source )
# Load conversions required for the dataset input
self.conversions = []
- for conv_elem in elem.findall( "conversion" ):
- name = conv_elem.get( "name" ) # name for commandline substitution
- conv_extensions = conv_elem.get( "type" ) # target datatype extension
- # FIXME: conv_extensions should be able to be an ordered list
- assert None not in [ name, type ], 'A name (%s) and type (%s) are required for explicit conversion' % ( name, type )
+ for name, conv_extensions in input_source.parse_conversion_tuples():
+ assert None not in [ name, conv_extensions ], 'A name (%s) and type (%s) are required for explicit conversion' % ( name, conv_extensions )
conv_types = [ tool.app.datatypes_registry.get_datatype_by_extension( conv_extensions.lower() ) ]
self.conversions.append( ( name, conv_extensions, conv_types ) )
@@ -2149,16 +2157,17 @@
"""
"""
- def __init__( self, tool, elem, trans=None ):
- super(DataCollectionToolParameter, self).__init__( tool, elem, trans )
- self.elem = elem
- self._parse_formats( trans, tool, elem )
+ def __init__( self, tool, input_source, trans=None ):
+ input_source = ensure_input_source( input_source )
+ super(DataCollectionToolParameter, self).__init__( tool, input_source, trans )
+ self._parse_formats( trans, tool, input_source )
+ self._collection_type = input_source.get("collection_type", None)
self.multiple = False # Accessed on DataToolParameter a lot, may want in future
- self._parse_options( elem ) # TODO: Review and test.
+ self._parse_options( input_source ) # TODO: Review and test.
@property
def collection_type( self ):
- return self.elem.get( "collection_type", None )
+ return self._collection_type
def _history_query( self, trans ):
dataset_collection_type_descriptions = trans.app.dataset_collections_service.collection_type_descriptions
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parameters/history_query.py
--- a/lib/galaxy/tools/parameters/history_query.py
+++ b/lib/galaxy/tools/parameters/history_query.py
@@ -11,10 +11,10 @@
self.collection_type_description = kwargs.get( "collection_type_description", None )
@staticmethod
- def from_parameter_elem( elem, collection_type_descriptions ):
+ def from_parameter( param, collection_type_descriptions ):
""" Take in a tool parameter element.
"""
- collection_type = elem.get( "collection_type", None )
+ collection_type = param.collection_type
if collection_type:
collection_type_description = collection_type_descriptions.for_collection_type( collection_type )
else:
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parameters/output_collect.py
--- a/lib/galaxy/tools/parameters/output_collect.py
+++ b/lib/galaxy/tools/parameters/output_collect.py
@@ -186,6 +186,10 @@
return map( lambda elem: DatasetCollector( **elem.attrib ), primary_dataset_elems )
+def dataset_collectors_from_list( discover_datasets_dicts ):
+ return map( lambda kwds: DatasetCollector( **kwds ), discover_datasets_dicts )
+
+
class DatasetCollector( object ):
def __init__( self, **kwargs ):
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parser/__init__.py
--- /dev/null
+++ b/lib/galaxy/tools/parser/__init__.py
@@ -0,0 +1,7 @@
+""" Package responsible for parsing tools from files/abstract tool sources.
+"""
+from .interface import ToolSource
+from .factory import get_tool_source
+from .factory import get_input_source
+
+__all__ = ["ToolSource", "get_tool_source", "get_input_source"]
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parser/factory.py
--- /dev/null
+++ b/lib/galaxy/tools/parser/factory.py
@@ -0,0 +1,47 @@
+from __future__ import absolute_import
+
+try:
+ from galaxy import eggs
+ eggs.require("PyYAML")
+except ImportError:
+ pass
+
+import yaml
+
+from .yaml import YamlToolSource
+from .xml import XmlToolSource
+from .xml import XmlInputSource
+from .interface import InputSource
+
+
+from galaxy.tools.loader import load_tool as load_tool_xml
+
+
+import logging
+log = logging.getLogger(__name__)
+
+
+def get_tool_source(config_file, enable_beta_formats=True):
+ if not enable_beta_formats:
+ tree = load_tool_xml(config_file)
+ root = tree.getroot()
+ return XmlToolSource(root)
+
+ if config_file.endswith(".yml"):
+ log.info("Loading tool from YAML - this is experimental - tool will not function in future.")
+ with open(config_file, "r") as f:
+ as_dict = yaml.load(f)
+ return YamlToolSource(as_dict)
+ else:
+ tree = load_tool_xml(config_file)
+ root = tree.getroot()
+ return XmlToolSource(root)
+
+
+def get_input_source(content):
+ """ Wraps XML elements in a XmlInputSource until everything
+ is consumed using the tool source interface.
+ """
+ if not isinstance(content, InputSource):
+ content = XmlInputSource(content)
+ return content
diff -r b9c5b028e389678c1a073f11c9aba5104ae17e89 -r 2dc383cbb700f9d8d6a30eb976b70e8dcf1832db lib/galaxy/tools/parser/interface.py
--- /dev/null
+++ b/lib/galaxy/tools/parser/interface.py
@@ -0,0 +1,246 @@
+from abc import ABCMeta
+from abc import abstractmethod
+
+NOT_IMPLEMENTED_MESSAGE = "Galaxy tool format does not yet support this tool feature."
+
+
+class ToolSource(object):
+ """ This interface represents an abstract source to parse tool
+ information from.
+ """
+ __metaclass__ = ABCMeta
+ default_is_multi_byte = False
+
+ @abstractmethod
+ def parse_id(self):
+ """ Parse an ID describing the abstract tool. This is not the
+ GUID tracked by the tool shed but the simple id (there may be
+ multiple tools loaded in Galaxy with this same simple id).
+ """
+
+ @abstractmethod
+ def parse_version(self):
+ """ Parse a version describing the abstract tool.
+ """
+
+ def parse_tool_module(self):
+ """ Load Tool class from a custom module. (Optional).
+
+ If not None, return pair containing module and class (as strings).
+ """
+ return None
+
+ def parse_action_module(self):
+ """ Load Tool class from a custom module. (Optional).
+
+ If not None, return pair containing module and class (as strings).
+ """
+ return None
+
+ def parse_tool_type(self):
+ """ Load simple tool type string (e.g. 'data_source', 'default').
+ """
+ return None
+
+ @abstractmethod
+ def parse_name(self):
+ """ Parse a short name for tool (required). """
+
+ @abstractmethod
+ def parse_description(self):
+ """ Parse a description for tool. Longer than name, shorted than help. """
+
+ def parse_is_multi_byte(self):
+ """ Parse is_multi_byte from tool - TODO: figure out what this is and
+ document.
+ """
+ return self.default_is_multi_byte
+
+ def parse_display_interface(self, default):
+ """ Parse display_interface - fallback to default for the tool type
+ (supplied as default parameter) if not specified.
+ """
+ return default
+
+ def parse_require_login(self, default):
+ """ Parse whether the tool requires login (as a bool).
+ """
+ return default
+
+ def parse_request_param_translation_elem(self):
+ """ Return an XML element describing require parameter translation.
+
+ If we wish to support this feature for non-XML based tools this should
+ be converted to return some sort of object interface instead of a RAW
+ XML element.
+ """
+ return None
+
+ @abstractmethod
+ def parse_command(self):
+ """ Return string contianing command to run.
+ """
+
+ @abstractmethod
+ def parse_interpreter(self):
+ """ Return string containing the interpreter to prepend to the command
+ (for instance this might be 'python' to run a Python wrapper located
+ adjacent to the tool).
+ """
+
+ def parse_redirect_url_params_elem(self):
+ """ Return an XML element describing redirect_url_params.
+
+ If we wish to support this feature for non-XML based tools this should
+ be converted to return some sort of object interface instead of a RAW
+ XML element.
+ """
+ return None
+
+ def parse_version_command(self):
+ """ Parse command used to determine version of primary application
+ driving the tool. Return None to not generate or record such a command.
+ """
+ return None
+
+ def parse_version_command_interpreter(self):
+ """ Parse command used to determine version of primary application
+ driving the tool. Return None to not generate or record such a command.
+ """
+ return None
+
+ def parse_parallelism(self):
+ """ Return a galaxy.jobs.ParallismInfo object describing task splitting
+ or None.
+ """
+ return None
+
+ def parse_hidden(self):
+ """ Return boolean indicating whether tool should be hidden in the tool menu.
+ """
+ return False
+
+ @abstractmethod
+ def parse_requirements_and_containers(self):
+ """ Return pair of ToolRequirement and ContainerDescription lists. """
+
+ @abstractmethod
+ def parse_input_pages(self):
+ """ Return a PagesSource representing inputs by page for tool. """
+
+ @abstractmethod
+ def parse_outputs(self, tool):
+ """ Return a list of ToolOutput objects.
+ """
+
+ @abstractmethod
+ def parse_stdio(self):
+ """ Builds lists of ToolStdioExitCode and ToolStdioRegex objects
+ to describe tool execution error conditions.
+ """
+ return [], []
+
+ def parse_tests_to_dict(self):
+ return {'tests': []}
+
+
+class PagesSource(object):
+ """ Contains a list of Pages - each a list of InputSources -
+ each item in the outer list representing a page of inputs.
+ Pages are deprecated so ideally this outer list will always
+ be exactly a singleton.
+ """
+ def __init__(self, page_sources):
+ self.page_sources = page_sources
+
+ @property
+ def inputs_defined(self):
+ return True
+
+
+class PageSource(object):
+ __metaclass__ = ABCMeta
+
+ def parse_display(self):
+ return None
+
+ @abstractmethod
+ def parse_input_sources(self):
+ """ Return a list of InputSource objects. """
+
+
+class InputSource(object):
+ __metaclass__ = ABCMeta
+ default_optional = False
+
+ def elem(self):
+ # For things in transition that still depend on XML - provide a way
+ # to grab it and just throw an error if feature is attempted to be
+ # used with other tool sources.
+ raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
+
+ @abstractmethod
+ def get(self, key, value=None):
+ """ Return simple named properties as string for this input source.
+ keys to be supported depend on the parameter type.
+ """
+
+ @abstractmethod
+ def get_bool(self, key, default):
+ """ Return simple named properties as boolean for this input source.
+ keys to be supported depend on the parameter type.
+ """
+
+ def parse_label(self):
+ return self.get("label")
+
+ def parse_help(self):
+ return self.get("label")
+
+ def parse_sanitizer_elem(self):
+ """ Return an XML description of sanitizers. This is a stop gap
+ until we can rework galaxy.tools.parameters.sanitize to not
+ explicitly depend on XML.
+ """
+ return None
+
+ def parse_validator_elems(self):
+ """ Return an XML description of sanitizers. This is a stop gap
+ until we can rework galaxy.tools.parameters.validation to not
+ explicitly depend on XML.
+ """
+ return []
+
+ def parse_optional(self, default=None):
+ """ Return boolean indicating wheter parameter is optional. """
+ if default is None:
+ default = self.default_optional
+ return self.get_bool( "optional", default )
+
+ def parse_dynamic_options(self, param):
+ """ Return a galaxy.tools.parameters.dynamic_options.DynamicOptions
+ if appropriate.
+ """
+ return None
+
+ def parse_static_options(self):
+ """ Return list of static options if this is a select type without
+ defining a dynamic options.
+ """
+ return []
+
+ def parse_conversion_tuples(self):
+ """ Return list of (name, extension) to describe explicit conversions.
+ """
+ return []
+
+ def parse_nested_inputs_source(self):
+ # For repeats
+ raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
+
+ def parse_test_input_source(self):
+ # For conditionals
+ raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
+
+ def parse_when_input_sources(self):
+ raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/39bc0ce6bb54/
Changeset: 39bc0ce6bb54
User: jmchilton
Date: 2014-12-11 04:51:45+00:00
Summary: PEP-8 fixes for tool related stuff.
Affected #: 1 file
diff -r fc60d0eeb22b28a2e61db5c6ec672a3dc5663845 -r 39bc0ce6bb543238c1ee67fc5491e65ed16fe6f0 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -1417,12 +1417,12 @@
def recurse_option_elems( cur_options, option_elems ):
for option_elem in option_elems:
selected = string_as_bool( option_elem.get( 'selected', False ) )
- cur_options.append( { 'name':option_elem.get( 'name' ), 'value': option_elem.get( 'value'), 'options':[], 'selected':selected } )
+ cur_options.append( { 'name': option_elem.get( 'name' ), 'value': option_elem.get( 'value' ), 'options': [], 'selected': selected } )
recurse_option_elems( cur_options[-1]['options'], option_elem.findall( 'option' ) )
ToolParameter.__init__( self, tool, elem )
self.multiple = string_as_bool( elem.get( 'multiple', False ) )
self.display = elem.get( 'display', None )
- self.hierarchy = elem.get( 'hierarchy', 'exact' ) #exact or recurse
+ self.hierarchy = elem.get( 'hierarchy', 'exact' ) # exact or recurse
self.separator = elem.get( 'separator', ',' )
from_file = elem.get( 'from_file', None )
if from_file:
@@ -1430,7 +1430,7 @@
from_file = os.path.join( tool.app.config.tool_data_path, from_file )
elem = XML( "<root>%s</root>" % open( from_file ).read() )
self.is_dynamic = False
- self.dynamic_options = elem.get( 'dynamic_options' , None )
+ self.dynamic_options = elem.get( 'dynamic_options', None )
if self.dynamic_options:
self.is_dynamic = True
self.options = []
@@ -1509,17 +1509,18 @@
return form_builder.TextArea( self.name, value=value )
else:
return form_builder.TextField( self.name, value=(value or "") )
- return form_builder.DrillDownField( self.name, self.multiple, self.display, self.refresh_on_change, self.get_options( trans, value, other_values ), value, refresh_on_change_values = self.refresh_on_change_values )
+ return form_builder.DrillDownField( self.name, self.multiple, self.display, self.refresh_on_change, self.get_options( trans, value, other_values ), value, refresh_on_change_values=self.refresh_on_change_values )
def from_html( self, value, trans=None, other_values={} ):
if self.need_late_validation( trans, other_values ):
if self.multiple:
- if value == '': #No option selected
+ if value == '': # No option selected
value = None
else:
value = value.split( "\n" )
return UnvalidatedValue( value )
- if not value: return None
+ if not value:
+ return None
if not isinstance( value, list ):
value = [value]
if not( self.repeat ) and len( value ) > 1:
@@ -1540,8 +1541,10 @@
if value == option['value']:
return option
rval = get_base_option( value, option['options'] )
- if rval: return rval
- return None #not found
+ if rval:
+ return rval
+ return None # not found
+
def recurse_option( option_list, option ):
if not option['options']:
option_list.append( option['value'] )
@@ -1931,7 +1934,7 @@
if value in [None, "None"]:
return None
if isinstance( value, str ) and value.find( "," ) > 0:
- value = [ int( value_part ) for value_part in value.split( "," ) ]
+ value = [ int( value_part ) for value_part in value.split( "," ) ]
if isinstance( value, list ):
rval = []
for single_value in value:
@@ -2368,6 +2371,7 @@
# return final dictionary
return d
+
class HiddenDataToolParameter( HiddenToolParameter, DataToolParameter ):
"""
Hidden parameter that behaves as a DataToolParameter. As with all hidden
https://bitbucket.org/galaxy/galaxy-central/commits/b9c5b028e389/
Changeset: b9c5b028e389
User: jmchilton
Date: 2014-12-11 04:51:45+00:00
Summary: More testing for parameter parsing.
Affected #: 1 file
diff -r 39bc0ce6bb543238c1ee67fc5491e65ed16fe6f0 -r b9c5b028e389678c1a073f11c9aba5104ae17e89 test/unit/tools/test_parameter_parsing.py
--- a/test/unit/tools/test_parameter_parsing.py
+++ b/test/unit/tools/test_parameter_parsing.py
@@ -177,9 +177,11 @@
<option value="b" selected="true">B</option></param>
""")
+ assert param.display is None
assert param.multiple is True
assert param.name == "selectp"
assert param.type == "select"
+ assert param.separator == ","
assert param.options is None
assert param.dynamic_options is None
@@ -196,7 +198,7 @@
def test_select_dynamic(self):
param = self._parameter_for(xml="""
- <param name="selectp" type="select" dynamic_options="cow">
+ <param name="selectp" type="select" dynamic_options="cow" display="checkboxes" separator="moo"></param>
""")
assert param.multiple is False
@@ -206,6 +208,9 @@
# assert not param.static_options
assert param.is_dynamic
+ assert param.display == "checkboxes"
+ assert param.separator == "moo"
+
def test_select_options_from(self):
param = self._parameter_for(xml="""
<param name="selectp" type="select">
@@ -229,8 +234,79 @@
assert param.name == "genomep"
assert param.static_options
- # Column and Data have sufficient test coverage in
- # other modules.
+ def test_column_params(self):
+ param = self._parameter_for(xml="""
+ <param name="col1" type="data_column" data_ref="input1">
+ </param>
+ """)
+ assert param.data_ref == "input1"
+ assert param.usecolnames is False
+ assert param.force_select is True
+ assert param.numerical is False
+
+ param = self._parameter_for(xml="""
+ <param name="col1" type="data_column" data_ref="input1" use_header_names="true" numerical="true" force_select="false">
+ </param>
+ """)
+ assert param.data_ref == "input1"
+ assert param.usecolnames is True
+ assert param.force_select is False
+ assert param.numerical is True
+
+ def test_data_param_no_validation(self):
+ param = self._parameter_for(xml="""
+ <param name="input" type="data">
+ </param>
+ """)
+ assert len(param.validators) == 1
+ param = self._parameter_for(xml="""
+ <param name="input" type="data" no_validation="true">
+ </param>
+ """)
+ assert len(param.validators) == 0
+
+ def test_data_param_dynamic_options(self):
+ param = self._parameter_for(xml="""
+ <param name="input" type="data" />
+ """)
+ assert param.options is None
+ assert param.options_filter_attribute is None
+
+ param = self._parameter_for(xml="""
+ <param name="input" type="data">
+ <options from_data_table="cow">
+ </options>
+ </param>
+ """)
+ assert param.options is not None
+ assert param.options_filter_attribute is None
+
+ param = self._parameter_for(xml="""
+ <param name="input" type="data">
+ <options from_data_table="cow" options_filter_attribute="cow">
+ </options>
+ </param>
+ """)
+ assert param.options is not None
+ assert param.options_filter_attribute == "cow"
+
+ def test_conversions(self):
+ param = self._parameter_for(xml="""
+ <param name="input" type="data" />
+ """)
+ assert param.conversions == []
+
+ param = self._parameter_for(xml="""
+ <param name="input" type="data">
+ <conversion name="foo" type="txt" />
+ <conversion name="foo2" type="bam" />
+ </param>
+ """)
+ assert param.conversions[0][0] == "foo"
+ assert param.conversions[0][1] == "txt"
+ assert param.conversions[1][0] == "foo2"
+ assert param.conversions[1][1] == "bam"
+
def test_drilldown(self):
param = self._parameter_for(xml="""
<param name="some_name" type="drill_down" display="checkbox" hierarchy="recurse" multiple="true">
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
[galaxyproject/usegalaxy-playbook] b78a21: Dropped VSU workshop slide from rotation. Almost ...
by GitHub 10 Dec '14
by GitHub 10 Dec '14
10 Dec '14
Branch: refs/heads/master
Home: https://github.com/galaxyproject/usegalaxy-playbook
Commit: b78a21b5954c3f89311c4223e403bea47f4ff677
https://github.com/galaxyproject/usegalaxy-playbook/commit/b78a21b5954c3f89…
Author: Dave Clements <clements(a)Clements-Galaxy.local>
Date: 2014-12-10 (Wed, 10 Dec 2014)
Changed paths:
M templates/galaxy/common/static/welcome.html.j2
Log Message:
-----------
Dropped VSU workshop slide from rotation. Almost full.
1
0
commit/galaxy-central: dannon: Merged in peterjc/galaxy-central-2/peterjc/clearer-error-messages-where-parameter-v-1418233351514 (pull request #604)
by commits-noreply@bitbucket.org 10 Dec '14
by commits-noreply@bitbucket.org 10 Dec '14
10 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fc60d0eeb22b/
Changeset: fc60d0eeb22b
User: dannon
Date: 2014-12-10 21:53:09+00:00
Summary: Merged in peterjc/galaxy-central-2/peterjc/clearer-error-messages-where-parameter-v-1418233351514 (pull request #604)
Clearer error messages where parameter validation fails
Affected #: 1 file
diff -r 0852880c97bdcc5730d0fc396c094fbadd296931 -r fc60d0eeb22b28a2e61db5c6ec672a3dc5663845 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -837,13 +837,14 @@
value = value.split()
return UnvalidatedValue( value )
legal_values = self.get_legal_values( trans, context )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
if isinstance( value, list ):
if not(self.repeat):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
for v in value:
if v not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, v))
rval.append( v )
return rval
else:
@@ -853,9 +854,9 @@
if self.optional:
return []
else:
- raise ValueError( "No option was selected but input is not optional." )
+ raise ValueError( "No option was selected for %s but input is not optional." % self.name)
if value not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, value))
return value
def to_html_value( self, value, app ):
@@ -869,7 +870,7 @@
return "None"
if isinstance( value, list ):
if not( self.repeat ):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
value = map( str, value )
else:
value = str( value )
@@ -1522,10 +1523,13 @@
if not isinstance( value, list ):
value = [value]
if not( self.repeat ) and len( value ) > 1:
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
+ legal_values = self.get_legal_values( trans, other_values )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
for val in value:
- if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected, please verify" )
+ if val not in legal_values:
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, val))
rval.append( val )
return rval
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
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9ba27992bfed/
Changeset: 9ba27992bfed
Branch: peterjc/clearer-error-messages-where-parameter-v-1418233351514
User: peterjc
Date: 2014-12-10 17:43:03+00:00
Summary: Clearer error messages where parameter validation fails
Affected #: 1 file
diff -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 -r 9ba27992bfed77dccbe59768d7de587f40f3c64b lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -839,11 +839,11 @@
legal_values = self.get_legal_values( trans, context )
if isinstance( value, list ):
if not(self.repeat):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
for v in value:
if v not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, v))
rval.append( v )
return rval
else:
@@ -853,9 +853,9 @@
if self.optional:
return []
else:
- raise ValueError( "No option was selected but input is not optional." )
+ raise ValueError( "No option was selected for %s but input is not optional." % self.name)
if value not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, value))
return value
def to_html_value( self, value, app ):
@@ -869,7 +869,7 @@
return "None"
if isinstance( value, list ):
if not( self.repeat ):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
value = map( str, value )
else:
value = str( value )
@@ -1522,10 +1522,10 @@
if not isinstance( value, list ):
value = [value]
if not( self.repeat ) and len( value ) > 1:
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
for val in value:
- if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected, please verify" )
+ if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, val))
rval.append( val )
return rval
https://bitbucket.org/galaxy/galaxy-central/commits/f77d82d21050/
Changeset: f77d82d21050
Branch: peterjc/clearer-error-messages-where-parameter-v-1418233351514
User: peterjc
Date: 2014-12-10 18:00:21+00:00
Summary: Clearer error messages if parameter as no legal values
Affected #: 1 file
diff -r 9ba27992bfed77dccbe59768d7de587f40f3c64b -r f77d82d2105034f021c82aee20bdffdaf862915a lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -837,6 +837,7 @@
value = value.split()
return UnvalidatedValue( value )
legal_values = self.get_legal_values( trans, context )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
if isinstance( value, list ):
if not(self.repeat):
assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
@@ -1524,8 +1525,11 @@
if not( self.repeat ) and len( value ) > 1:
assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
+ legal_values = self.get_legal_values( trans, other_values )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
for val in value:
- if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, val))
+ if val not in legal_values:
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, val))
rval.append( val )
return rval
https://bitbucket.org/galaxy/galaxy-central/commits/fc60d0eeb22b/
Changeset: fc60d0eeb22b
User: dannon
Date: 2014-12-10 21:53:09+00:00
Summary: Merged in peterjc/galaxy-central-2/peterjc/clearer-error-messages-where-parameter-v-1418233351514 (pull request #604)
Clearer error messages where parameter validation fails
Affected #: 1 file
diff -r 0852880c97bdcc5730d0fc396c094fbadd296931 -r fc60d0eeb22b28a2e61db5c6ec672a3dc5663845 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -837,13 +837,14 @@
value = value.split()
return UnvalidatedValue( value )
legal_values = self.get_legal_values( trans, context )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
if isinstance( value, list ):
if not(self.repeat):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
for v in value:
if v not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, v))
rval.append( v )
return rval
else:
@@ -853,9 +854,9 @@
if self.optional:
return []
else:
- raise ValueError( "No option was selected but input is not optional." )
+ raise ValueError( "No option was selected for %s but input is not optional." % self.name)
if value not in legal_values:
- raise ValueError( "An invalid option was selected, please verify" )
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, value))
return value
def to_html_value( self, value, app ):
@@ -869,7 +870,7 @@
return "None"
if isinstance( value, list ):
if not( self.repeat ):
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
value = map( str, value )
else:
value = str( value )
@@ -1522,10 +1523,13 @@
if not isinstance( value, list ):
value = [value]
if not( self.repeat ) and len( value ) > 1:
- assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
+ assert self.multiple, "Multiple values provided but parameter %s is not expecting multiple values" % self.name
rval = []
+ legal_values = self.get_legal_values( trans, other_values )
+ assert legal_values, "Parameter %s requires a value, but has no legal values defined" % self.name
for val in value:
- if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected, please verify" )
+ if val not in legal_values:
+ raise ValueError( "An invalid option was selected for %s, %r, please verify" % (self.name, val))
rval.append( val )
return rval
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: carlfeberhard: Client: update libs/underscore.js to 1.7
by commits-noreply@bitbucket.org 10 Dec '14
by commits-noreply@bitbucket.org 10 Dec '14
10 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0852880c97bd/
Changeset: 0852880c97bd
User: carlfeberhard
Date: 2014-12-10 21:23:21+00:00
Summary: Client: update libs/underscore.js to 1.7
Affected #: 3 files
diff -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 -r 0852880c97bdcc5730d0fc396c094fbadd296931 client/galaxy/scripts/libs/underscore.js
--- a/client/galaxy/scripts/libs/underscore.js
+++ b/client/galaxy/scripts/libs/underscore.js
@@ -1,6 +1,6 @@
-// Underscore.js 1.4.4
+// Underscore.js 1.7.0
// http://underscorejs.org
-// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function() {
@@ -8,37 +8,26 @@
// Baseline setup
// --------------
- // Establish the root object, `window` in the browser, or `global` on the server.
+ // Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
- // Establish the object that gets returned to break out of a loop iteration.
- var breaker = {};
-
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
- var push = ArrayProto.push,
- slice = ArrayProto.slice,
- concat = ArrayProto.concat,
- toString = ObjProto.toString,
- hasOwnProperty = ObjProto.hasOwnProperty;
+ var
+ push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ concat = ArrayProto.concat,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
- nativeForEach = ArrayProto.forEach,
- nativeMap = ArrayProto.map,
- nativeReduce = ArrayProto.reduce,
- nativeReduceRight = ArrayProto.reduceRight,
- nativeFilter = ArrayProto.filter,
- nativeEvery = ArrayProto.every,
- nativeSome = ArrayProto.some,
- nativeIndexOf = ArrayProto.indexOf,
- nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
@@ -52,8 +41,7 @@
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
- // the browser, add `_` as a global object via a string identifier,
- // for Closure Compiler "advanced" mode.
+ // the browser, add `_` as a global object.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
@@ -64,98 +52,125 @@
}
// Current version.
- _.VERSION = '1.4.4';
+ _.VERSION = '1.7.0';
+
+ // Internal function that returns an efficient (for current engines) version
+ // of the passed-in callback, to be repeatedly applied in other Underscore
+ // functions.
+ var createCallback = function(func, context, argCount) {
+ if (context === void 0) return func;
+ switch (argCount == null ? 3 : argCount) {
+ case 1: return function(value) {
+ return func.call(context, value);
+ };
+ case 2: return function(value, other) {
+ return func.call(context, value, other);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(context, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(context, accumulator, value, index, collection);
+ };
+ }
+ return function() {
+ return func.apply(context, arguments);
+ };
+ };
+
+ // A mostly-internal function to generate callbacks that can be applied
+ // to each element in a collection, returning the desired result — either
+ // identity, an arbitrary callback, a property matcher, or a property accessor.
+ _.iteratee = function(value, context, argCount) {
+ if (value == null) return _.identity;
+ if (_.isFunction(value)) return createCallback(value, context, argCount);
+ if (_.isObject(value)) return _.matches(value);
+ return _.property(value);
+ };
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
- // Handles objects with the built-in `forEach`, arrays, and raw objects.
- // Delegates to **ECMAScript 5**'s native `forEach` if available.
- var each = _.each = _.forEach = function(obj, iterator, context) {
- if (obj == null) return;
- if (nativeForEach && obj.forEach === nativeForEach) {
- obj.forEach(iterator, context);
- } else if (obj.length === +obj.length) {
- for (var i = 0, l = obj.length; i < l; i++) {
- if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ // Handles raw objects in addition to array-likes. Treats all
+ // sparse array-likes as if they were dense.
+ _.each = _.forEach = function(obj, iteratee, context) {
+ if (obj == null) return obj;
+ iteratee = createCallback(iteratee, context);
+ var i, length = obj.length;
+ if (length === +length) {
+ for (i = 0; i < length; i++) {
+ iteratee(obj[i], i, obj);
}
} else {
- for (var key in obj) {
- if (_.has(obj, key)) {
- if (iterator.call(context, obj[key], key, obj) === breaker) return;
- }
+ var keys = _.keys(obj);
+ for (i = 0, length = keys.length; i < length; i++) {
+ iteratee(obj[keys[i]], keys[i], obj);
}
}
+ return obj;
};
- // Return the results of applying the iterator to each element.
- // Delegates to **ECMAScript 5**'s native `map` if available.
- _.map = _.collect = function(obj, iterator, context) {
- var results = [];
- if (obj == null) return results;
- if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
- each(obj, function(value, index, list) {
- results.push(iterator.call(context, value, index, list));
- });
+ // Return the results of applying the iteratee to each element.
+ _.map = _.collect = function(obj, iteratee, context) {
+ if (obj == null) return [];
+ iteratee = _.iteratee(iteratee, context);
+ var keys = obj.length !== +obj.length && _.keys(obj),
+ length = (keys || obj).length,
+ results = Array(length),
+ currentKey;
+ for (var index = 0; index < length; index++) {
+ currentKey = keys ? keys[index] : index;
+ results[index] = iteratee(obj[currentKey], currentKey, obj);
+ }
return results;
};
var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`,
- // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
- _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
- var initial = arguments.length > 2;
+ // or `foldl`.
+ _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
if (obj == null) obj = [];
- if (nativeReduce && obj.reduce === nativeReduce) {
- if (context) iterator = _.bind(iterator, context);
- return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ iteratee = createCallback(iteratee, context, 4);
+ var keys = obj.length !== +obj.length && _.keys(obj),
+ length = (keys || obj).length,
+ index = 0, currentKey;
+ if (arguments.length < 3) {
+ if (!length) throw new TypeError(reduceError);
+ memo = obj[keys ? keys[index++] : index++];
}
- each(obj, function(value, index, list) {
- if (!initial) {
- memo = value;
- initial = true;
- } else {
- memo = iterator.call(context, memo, value, index, list);
- }
- });
- if (!initial) throw new TypeError(reduceError);
+ for (; index < length; index++) {
+ currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
+ }
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
- // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
- _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
- var initial = arguments.length > 2;
+ _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
if (obj == null) obj = [];
- if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
- if (context) iterator = _.bind(iterator, context);
- return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ iteratee = createCallback(iteratee, context, 4);
+ var keys = obj.length !== + obj.length && _.keys(obj),
+ index = (keys || obj).length,
+ currentKey;
+ if (arguments.length < 3) {
+ if (!index) throw new TypeError(reduceError);
+ memo = obj[keys ? keys[--index] : --index];
}
- var length = obj.length;
- if (length !== +length) {
- var keys = _.keys(obj);
- length = keys.length;
+ while (index--) {
+ currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
- each(obj, function(value, index, list) {
- index = keys ? keys[--length] : --length;
- if (!initial) {
- memo = obj[index];
- initial = true;
- } else {
- memo = iterator.call(context, memo, obj[index], index, list);
- }
- });
- if (!initial) throw new TypeError(reduceError);
return memo;
};
// Return the first value which passes a truth test. Aliased as `detect`.
- _.find = _.detect = function(obj, iterator, context) {
+ _.find = _.detect = function(obj, predicate, context) {
var result;
- any(obj, function(value, index, list) {
- if (iterator.call(context, value, index, list)) {
+ predicate = _.iteratee(predicate, context);
+ _.some(obj, function(value, index, list) {
+ if (predicate(value, index, list)) {
result = value;
return true;
}
@@ -164,61 +179,58 @@
};
// Return all the elements that pass a truth test.
- // Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
- _.filter = _.select = function(obj, iterator, context) {
+ _.filter = _.select = function(obj, predicate, context) {
var results = [];
if (obj == null) return results;
- if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
- each(obj, function(value, index, list) {
- if (iterator.call(context, value, index, list)) results.push(value);
+ predicate = _.iteratee(predicate, context);
+ _.each(obj, function(value, index, list) {
+ if (predicate(value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
- _.reject = function(obj, iterator, context) {
- return _.filter(obj, function(value, index, list) {
- return !iterator.call(context, value, index, list);
- }, context);
+ _.reject = function(obj, predicate, context) {
+ return _.filter(obj, _.negate(_.iteratee(predicate)), context);
};
// Determine whether all of the elements match a truth test.
- // Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
- _.every = _.all = function(obj, iterator, context) {
- iterator || (iterator = _.identity);
- var result = true;
- if (obj == null) return result;
- if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
- each(obj, function(value, index, list) {
- if (!(result = result && iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.every = _.all = function(obj, predicate, context) {
+ if (obj == null) return true;
+ predicate = _.iteratee(predicate, context);
+ var keys = obj.length !== +obj.length && _.keys(obj),
+ length = (keys || obj).length,
+ index, currentKey;
+ for (index = 0; index < length; index++) {
+ currentKey = keys ? keys[index] : index;
+ if (!predicate(obj[currentKey], currentKey, obj)) return false;
+ }
+ return true;
};
// Determine if at least one element in the object matches a truth test.
- // Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
- var any = _.some = _.any = function(obj, iterator, context) {
- iterator || (iterator = _.identity);
- var result = false;
- if (obj == null) return result;
- if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
- each(obj, function(value, index, list) {
- if (result || (result = iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.some = _.any = function(obj, predicate, context) {
+ if (obj == null) return false;
+ predicate = _.iteratee(predicate, context);
+ var keys = obj.length !== +obj.length && _.keys(obj),
+ length = (keys || obj).length,
+ index, currentKey;
+ for (index = 0; index < length; index++) {
+ currentKey = keys ? keys[index] : index;
+ if (predicate(obj[currentKey], currentKey, obj)) return true;
+ }
+ return false;
};
// Determine if the array or object contains a given value (using `===`).
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
if (obj == null) return false;
- if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
- return any(obj, function(value) {
- return value === target;
- });
+ if (obj.length !== +obj.length) obj = _.values(obj);
+ return _.indexOf(obj, target) >= 0;
};
// Invoke a method (with arguments) on every item in a collection.
@@ -232,83 +244,104 @@
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
- return _.map(obj, function(value){ return value[key]; });
+ return _.map(obj, _.property(key));
};
// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
- _.where = function(obj, attrs, first) {
- if (_.isEmpty(attrs)) return first ? void 0 : [];
- return _[first ? 'find' : 'filter'](obj, function(value) {
- for (var key in attrs) {
- if (attrs[key] !== value[key]) return false;
- }
- return true;
- });
+ _.where = function(obj, attrs) {
+ return _.filter(obj, _.matches(attrs));
};
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
- return _.where(obj, attrs, true);
+ return _.find(obj, _.matches(attrs));
};
- // Return the maximum element or (element-based computation).
- // Can't optimize arrays of integers longer than 65,535 elements.
- // See: https://bugs.webkit.org/show_bug.cgi?id=80797
- _.max = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
- return Math.max.apply(Math, obj);
+ // Return the maximum element (or element-based computation).
+ _.max = function(obj, iteratee, context) {
+ var result = -Infinity, lastComputed = -Infinity,
+ value, computed;
+ if (iteratee == null && obj != null) {
+ obj = obj.length === +obj.length ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = _.iteratee(iteratee, context);
+ _.each(obj, function(value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
}
- if (!iterator && _.isEmpty(obj)) return -Infinity;
- var result = {computed : -Infinity, value: -Infinity};
- each(obj, function(value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed >= result.computed && (result = {value : value, computed : computed});
- });
- return result.value;
+ return result;
};
// Return the minimum element (or element-based computation).
- _.min = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
- return Math.min.apply(Math, obj);
+ _.min = function(obj, iteratee, context) {
+ var result = Infinity, lastComputed = Infinity,
+ value, computed;
+ if (iteratee == null && obj != null) {
+ obj = obj.length === +obj.length ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = _.iteratee(iteratee, context);
+ _.each(obj, function(value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed < lastComputed || computed === Infinity && result === Infinity) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
}
- if (!iterator && _.isEmpty(obj)) return Infinity;
- var result = {computed : Infinity, value: Infinity};
- each(obj, function(value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed < result.computed && (result = {value : value, computed : computed});
- });
- return result.value;
+ return result;
};
- // Shuffle an array.
+ // Shuffle a collection, using the modern version of the
+ // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) {
- var rand;
- var index = 0;
- var shuffled = [];
- each(obj, function(value) {
- rand = _.random(index++);
- shuffled[index - 1] = shuffled[rand];
- shuffled[rand] = value;
- });
+ var set = obj && obj.length === +obj.length ? obj : _.values(obj);
+ var length = set.length;
+ var shuffled = Array(length);
+ for (var index = 0, rand; index < length; index++) {
+ rand = _.random(0, index);
+ if (rand !== index) shuffled[index] = shuffled[rand];
+ shuffled[rand] = set[index];
+ }
return shuffled;
};
- // An internal function to generate lookup iterators.
- var lookupIterator = function(value) {
- return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+ // Sample **n** random values from a collection.
+ // If **n** is not specified, returns a single random element.
+ // The internal `guard` argument allows it to work with `map`.
+ _.sample = function(obj, n, guard) {
+ if (n == null || guard) {
+ if (obj.length !== +obj.length) obj = _.values(obj);
+ return obj[_.random(obj.length - 1)];
+ }
+ return _.shuffle(obj).slice(0, Math.max(0, n));
};
- // Sort the object's values by a criterion produced by an iterator.
- _.sortBy = function(obj, value, context) {
- var iterator = lookupIterator(value);
+ // Sort the object's values by a criterion produced by an iteratee.
+ _.sortBy = function(obj, iteratee, context) {
+ iteratee = _.iteratee(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
- value : value,
- index : index,
- criteria : iterator.call(context, value, index, list)
+ value: value,
+ index: index,
+ criteria: iteratee(value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
@@ -317,53 +350,56 @@
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
- return left.index < right.index ? -1 : 1;
+ return left.index - right.index;
}), 'value');
};
// An internal function used for aggregate "group by" operations.
- var group = function(obj, value, context, behavior) {
- var result = {};
- var iterator = lookupIterator(value == null ? _.identity : value);
- each(obj, function(value, index) {
- var key = iterator.call(context, value, index, obj);
- behavior(result, key, value);
- });
- return result;
+ var group = function(behavior) {
+ return function(obj, iteratee, context) {
+ var result = {};
+ iteratee = _.iteratee(iteratee, context);
+ _.each(obj, function(value, index) {
+ var key = iteratee(value, index, obj);
+ behavior(result, value, key);
+ });
+ return result;
+ };
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
- _.groupBy = function(obj, value, context) {
- return group(obj, value, context, function(result, key, value) {
- (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
- });
- };
+ _.groupBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+ });
+
+ // Indexes the object's values by a criterion, similar to `groupBy`, but for
+ // when you know that your index values will be unique.
+ _.indexBy = group(function(result, value, key) {
+ result[key] = value;
+ });
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
- _.countBy = function(obj, value, context) {
- return group(obj, value, context, function(result, key) {
- if (!_.has(result, key)) result[key] = 0;
- result[key]++;
- });
- };
+ _.countBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key]++; else result[key] = 1;
+ });
// Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search.
- _.sortedIndex = function(array, obj, iterator, context) {
- iterator = iterator == null ? _.identity : lookupIterator(iterator);
- var value = iterator.call(context, obj);
+ _.sortedIndex = function(array, obj, iteratee, context) {
+ iteratee = _.iteratee(iteratee, context, 1);
+ var value = iteratee(obj);
var low = 0, high = array.length;
while (low < high) {
- var mid = (low + high) >>> 1;
- iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+ var mid = low + high >>> 1;
+ if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
};
- // Safely convert anything iterable into a real, live array.
+ // Safely create a real, live array from anything iterable.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
@@ -374,7 +410,18 @@
// Return the number of elements in an object.
_.size = function(obj) {
if (obj == null) return 0;
- return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ return obj.length === +obj.length ? obj.length : _.keys(obj).length;
+ };
+
+ // Split a collection into two arrays: one whose elements all satisfy the given
+ // predicate, and one whose elements all do not satisfy the predicate.
+ _.partition = function(obj, predicate, context) {
+ predicate = _.iteratee(predicate, context);
+ var pass = [], fail = [];
+ _.each(obj, function(value, key, obj) {
+ (predicate(value, key, obj) ? pass : fail).push(value);
+ });
+ return [pass, fail];
};
// Array Functions
@@ -385,7 +432,9 @@
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0;
- return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ if (n == null || guard) return array[0];
+ if (n < 0) return [];
+ return slice.call(array, 0, n);
};
// Returns everything but the last entry of the array. Especially useful on
@@ -393,18 +442,15 @@
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
- return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+ return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if (array == null) return void 0;
- if ((n != null) && !guard) {
- return slice.call(array, Math.max(array.length - n, 0));
- } else {
- return array[array.length - 1];
- }
+ if (n == null || guard) return array[array.length - 1];
+ return slice.call(array, Math.max(array.length - n, 0));
};
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
@@ -412,7 +458,7 @@
// the rest N values in the array. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = _.drop = function(array, n, guard) {
- return slice.call(array, (n == null) || guard ? 1 : n);
+ return slice.call(array, n == null || guard ? 1 : n);
};
// Trim out all falsy values from an array.
@@ -421,20 +467,26 @@
};
// Internal implementation of a recursive `flatten` function.
- var flatten = function(input, shallow, output) {
- each(input, function(value) {
- if (_.isArray(value)) {
- shallow ? push.apply(output, value) : flatten(value, shallow, output);
+ var flatten = function(input, shallow, strict, output) {
+ if (shallow && _.every(input, _.isArray)) {
+ return concat.apply(output, input);
+ }
+ for (var i = 0, length = input.length; i < length; i++) {
+ var value = input[i];
+ if (!_.isArray(value) && !_.isArguments(value)) {
+ if (!strict) output.push(value);
+ } else if (shallow) {
+ push.apply(output, value);
} else {
- output.push(value);
+ flatten(value, shallow, strict, output);
}
- });
+ }
return output;
};
- // Return a completely flattened version of an array.
+ // Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
- return flatten(array, shallow, []);
+ return flatten(array, shallow, false, []);
};
// Return a version of the array that does not contain the specified value(s).
@@ -445,86 +497,85 @@
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
- _.uniq = _.unique = function(array, isSorted, iterator, context) {
- if (_.isFunction(isSorted)) {
- context = iterator;
- iterator = isSorted;
+ _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+ if (array == null) return [];
+ if (!_.isBoolean(isSorted)) {
+ context = iteratee;
+ iteratee = isSorted;
isSorted = false;
}
- var initial = iterator ? _.map(array, iterator, context) : array;
- var results = [];
+ if (iteratee != null) iteratee = _.iteratee(iteratee, context);
+ var result = [];
var seen = [];
- each(initial, function(value, index) {
- if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
- seen.push(value);
- results.push(array[index]);
+ for (var i = 0, length = array.length; i < length; i++) {
+ var value = array[i];
+ if (isSorted) {
+ if (!i || seen !== value) result.push(value);
+ seen = value;
+ } else if (iteratee) {
+ var computed = iteratee(value, i, array);
+ if (_.indexOf(seen, computed) < 0) {
+ seen.push(computed);
+ result.push(value);
+ }
+ } else if (_.indexOf(result, value) < 0) {
+ result.push(value);
}
- });
- return results;
+ }
+ return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
- return _.uniq(concat.apply(ArrayProto, arguments));
+ return _.uniq(flatten(arguments, true, true, []));
};
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
- var rest = slice.call(arguments, 1);
- return _.filter(_.uniq(array), function(item) {
- return _.every(rest, function(other) {
- return _.indexOf(other, item) >= 0;
- });
- });
+ if (array == null) return [];
+ var result = [];
+ var argsLength = arguments.length;
+ for (var i = 0, length = array.length; i < length; i++) {
+ var item = array[i];
+ if (_.contains(result, item)) continue;
+ for (var j = 1; j < argsLength; j++) {
+ if (!_.contains(arguments[j], item)) break;
+ }
+ if (j === argsLength) result.push(item);
+ }
+ return result;
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
- var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
- return _.filter(array, function(value){ return !_.contains(rest, value); });
+ var rest = flatten(slice.call(arguments, 1), true, true, []);
+ return _.filter(array, function(value){
+ return !_.contains(rest, value);
+ });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
- _.zip = function() {
- var args = slice.call(arguments);
- var length = _.max(_.pluck(args, 'length'));
- var results = new Array(length);
+ _.zip = function(array) {
+ if (array == null) return [];
+ var length = _.max(arguments, 'length').length;
+ var results = Array(length);
for (var i = 0; i < length; i++) {
- results[i] = _.pluck(args, "" + i);
+ results[i] = _.pluck(arguments, i);
}
return results;
};
- // The inverse operation to `_.zip`. If given an array of pairs it
- // returns an array of the paired elements split into two left and
- // right element arrays, if given an array of triples it returns a
- // three element array and so on. For example, `_.unzip` given
- // `[['a',1],['b',2],['c',3]]` returns the array
- // [['a','b','c'],[1,2,3]].
- _.unzip = function(tuples) {
- var results = [];
- _.each(tuples, function (tuple, tupleIndex) {
- _.each(tuple, function (value, itemIndex) {
- if (results.length <= itemIndex) {
- results[itemIndex] = [];
- }
- results[itemIndex][tupleIndex] = value;
- });
- });
- return results;
- };
-
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
if (list == null) return {};
var result = {};
- for (var i = 0, l = list.length; i < l; i++) {
+ for (var i = 0, length = list.length; i < length; i++) {
if (values) {
result[list[i]] = values[i];
} else {
@@ -534,37 +585,32 @@
return result;
};
- // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
- // we need this function. Return the position of the first occurrence of an
- // item in an array, or -1 if the item is not included in the array.
- // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // Return the position of the first occurrence of an item in an array,
+ // or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
- var i = 0, l = array.length;
+ var i = 0, length = array.length;
if (isSorted) {
if (typeof isSorted == 'number') {
- i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+ i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
} else {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
- for (; i < l; i++) if (array[i] === item) return i;
+ for (; i < length; i++) if (array[i] === item) return i;
return -1;
};
- // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item, from) {
if (array == null) return -1;
- var hasIndex = from != null;
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
- return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+ var idx = array.length;
+ if (typeof from == 'number') {
+ idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
}
- var i = (hasIndex ? from : array.length);
- while (i--) if (array[i] === item) return i;
+ while (--idx >= 0) if (array[idx] === item) return idx;
return -1;
};
@@ -576,15 +622,13 @@
stop = start || 0;
start = 0;
}
- step = arguments[2] || 1;
+ step = step || 1;
- var len = Math.max(Math.ceil((stop - start) / step), 0);
- var idx = 0;
- var range = new Array(len);
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
+ var range = Array(length);
- while(idx < len) {
- range[idx++] = start;
- start += step;
+ for (var idx = 0; idx < length; idx++, start += step) {
+ range[idx] = start;
}
return range;
@@ -594,60 +638,76 @@
// ------------------
// Reusable constructor function for prototype setting.
- var ctor = function(){};
+ var Ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
var args, bound;
- if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
- if (!_.isFunction(func)) throw new TypeError;
+ if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
args = slice.call(arguments, 2);
- return bound = function() {
+ bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
- ctor.prototype = func.prototype;
- var self = new ctor;
- ctor.prototype = null;
+ Ctor.prototype = func.prototype;
+ var self = new Ctor;
+ Ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
- if (Object(result) === result) return result;
+ if (_.isObject(result)) return result;
return self;
};
+ return bound;
+ };
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context. _ acts
+ // as a placeholder, allowing any combination of arguments to be pre-filled.
+ _.partial = function(func) {
+ var boundArgs = slice.call(arguments, 1);
+ return function() {
+ var position = 0;
+ var args = boundArgs.slice();
+ for (var i = 0, length = args.length; i < length; i++) {
+ if (args[i] === _) args[i] = arguments[position++];
+ }
+ while (position < arguments.length) args.push(arguments[position++]);
+ return func.apply(this, args);
+ };
};
- // Partially apply a function by creating a version that has had some of its
- // arguments pre-filled, without changing its dynamic `this` context.
- _.partial = function(func) {
- var args = slice.call(arguments, 1);
- return function() {
- return func.apply(this, args.concat(slice.call(arguments)));
- };
- };
-
- // Bind all of an object's methods to that object. Useful for ensuring that
- // all callbacks defined on an object belong to it.
+ // Bind a number of an object's methods to that object. Remaining arguments
+ // are the method names to be bound. Useful for ensuring that all callbacks
+ // defined on an object belong to it.
_.bindAll = function(obj) {
- var funcs = slice.call(arguments, 1);
- if (funcs.length === 0) throw new Error("bindAll must be passed function names");
- each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ var i, length = arguments.length, key;
+ if (length <= 1) throw new Error('bindAll must be passed function names');
+ for (i = 1; i < length; i++) {
+ key = arguments[i];
+ obj[key] = _.bind(obj[key], obj);
+ }
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
- var memo = {};
- hasher || (hasher = _.identity);
- return function() {
- var key = hasher.apply(this, arguments);
- return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ var memoize = function(key) {
+ var cache = memoize.cache;
+ var address = hasher ? hasher.apply(this, arguments) : key;
+ if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+ return cache[address];
};
+ memoize.cache = {};
+ return memoize;
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
- return setTimeout(function(){ return func.apply(null, args); }, wait);
+ return setTimeout(function(){
+ return func.apply(null, args);
+ }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
@@ -657,27 +717,34 @@
};
// Returns a function, that, when invoked, will only be triggered at most once
- // during a given window of time.
- _.throttle = function(func, wait, immediate) {
- var context, args, timeout, result;
+ // during a given window of time. Normally, the throttled function will run
+ // as much as it can, without ever going more than once per `wait` duration;
+ // but if you'd like to disable the execution on the leading edge, pass
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
+ _.throttle = function(func, wait, options) {
+ var context, args, result;
+ var timeout = null;
var previous = 0;
+ if (!options) options = {};
var later = function() {
- previous = new Date;
+ previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
+ if (!timeout) context = args = null;
};
return function() {
- var now = new Date;
- if (!previous && immediate === false) previous = now;
+ var now = _.now();
+ if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
- if (remaining <= 0) {
+ if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
- } else if (!timeout) {
+ if (!timeout) context = args = null;
+ } else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
@@ -689,61 +756,66 @@
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
- var timeout, result;
+ var timeout, args, context, timestamp, result;
+
+ var later = function() {
+ var last = _.now() - timestamp;
+
+ if (last < wait && last > 0) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ }
+ }
+ };
+
return function() {
- var context = this, args = arguments;
- var later = function() {
- timeout = null;
- if (!immediate) result = func.apply(context, args);
- };
+ context = this;
+ args = arguments;
+ timestamp = _.now();
var callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) result = func.apply(context, args);
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+
return result;
};
};
- // Returns a function that will be executed at most one time, no matter how
- // often you call it. Useful for lazy initialization.
- _.once = function(func) {
- var ran = false, memo;
- return function() {
- if (ran) return memo;
- ran = true;
- memo = func.apply(this, arguments);
- func = null;
- return memo;
- };
- };
-
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
+ return _.partial(wrapper, func);
+ };
+
+ // Returns a negated version of the passed-in predicate.
+ _.negate = function(predicate) {
return function() {
- var args = [func];
- push.apply(args, arguments);
- return wrapper.apply(this, args);
+ return !predicate.apply(this, arguments);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
- var funcs = arguments;
+ var args = arguments;
+ var start = args.length - 1;
return function() {
- var args = arguments;
- for (var i = funcs.length - 1; i >= 0; i--) {
- args = [funcs[i].apply(this, args)];
- }
- return args[0];
+ var i = start;
+ var result = args[start].apply(this, arguments);
+ while (i--) result = args[i].call(this, result);
+ return result;
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
- if (times <= 0) return func();
return function() {
if (--times < 1) {
return func.apply(this, arguments);
@@ -751,13 +823,31 @@
};
};
+ // Returns a function that will only be executed before being called N times.
+ _.before = function(times, func) {
+ var memo;
+ return function() {
+ if (--times > 0) {
+ memo = func.apply(this, arguments);
+ } else {
+ func = null;
+ }
+ return memo;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = _.partial(_.before, 2);
+
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
- _.keys = nativeKeys || function(obj) {
- if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ _.keys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys;
@@ -765,22 +855,33 @@
// Retrieve the values of an object's properties.
_.values = function(obj) {
- var values = [];
- for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var values = Array(length);
+ for (var i = 0; i < length; i++) {
+ values[i] = obj[keys[i]];
+ }
return values;
};
// Convert an object into a list of `[key, value]` pairs.
_.pairs = function(obj) {
- var pairs = [];
- for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var pairs = Array(length);
+ for (var i = 0; i < length; i++) {
+ pairs[i] = [keys[i], obj[keys[i]]];
+ }
return pairs;
};
// Invert the keys and values of an object. The values must be serializable.
_.invert = function(obj) {
var result = {};
- for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+ var keys = _.keys(obj);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ result[obj[keys[i]]] = keys[i];
+ }
return result;
};
@@ -796,45 +897,62 @@
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
- each(slice.call(arguments, 1), function(source) {
- if (source) {
- for (var prop in source) {
- obj[prop] = source[prop];
+ if (!_.isObject(obj)) return obj;
+ var source, prop;
+ for (var i = 1, length = arguments.length; i < length; i++) {
+ source = arguments[i];
+ for (prop in source) {
+ if (hasOwnProperty.call(source, prop)) {
+ obj[prop] = source[prop];
}
}
- });
+ }
return obj;
};
// Return a copy of the object only containing the whitelisted properties.
- _.pick = function(obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
- each(keys, function(key) {
- if (key in obj) copy[key] = obj[key];
- });
- return copy;
+ _.pick = function(obj, iteratee, context) {
+ var result = {}, key;
+ if (obj == null) return result;
+ if (_.isFunction(iteratee)) {
+ iteratee = createCallback(iteratee, context);
+ for (key in obj) {
+ var value = obj[key];
+ if (iteratee(value, key, obj)) result[key] = value;
+ }
+ } else {
+ var keys = concat.apply([], slice.call(arguments, 1));
+ obj = new Object(obj);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ key = keys[i];
+ if (key in obj) result[key] = obj[key];
+ }
+ }
+ return result;
};
// Return a copy of the object without the blacklisted properties.
- _.omit = function(obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
- for (var key in obj) {
- if (!_.contains(keys, key)) copy[key] = obj[key];
+ _.omit = function(obj, iteratee, context) {
+ if (_.isFunction(iteratee)) {
+ iteratee = _.negate(iteratee);
+ } else {
+ var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
+ iteratee = function(value, key) {
+ return !_.contains(keys, key);
+ };
}
- return copy;
+ return _.pick(obj, iteratee, context);
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
- each(slice.call(arguments, 1), function(source) {
- if (source) {
- for (var prop in source) {
- if (obj[prop] === void 0) obj[prop] = source[prop];
- }
+ if (!_.isObject(obj)) return obj;
+ for (var i = 1, length = arguments.length; i < length; i++) {
+ var source = arguments[i];
+ for (var prop in source) {
+ if (obj[prop] === void 0) obj[prop] = source[prop];
}
- });
+ }
return obj;
};
@@ -855,8 +973,8 @@
// Internal recursive comparison function for `isEqual`.
var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
- // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
- if (a === b) return a !== 0 || 1 / a == 1 / b;
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) return a !== 0 || 1 / a === 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
@@ -864,29 +982,27 @@
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
- if (className != toString.call(b)) return false;
+ if (className !== toString.call(b)) return false;
switch (className) {
- // Strings, numbers, dates, and booleans are compared by value.
+ // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+ case '[object RegExp]':
+ // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
- return a == String(b);
+ return '' + a === '' + b;
case '[object Number]':
- // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
- // other numeric values.
- return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+ // `NaN`s are equivalent, but non-reflexive.
+ // Object(NaN) is equivalent to NaN
+ if (+a !== +a) return +b !== +b;
+ // An `egal` comparison is performed for other numeric values.
+ return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
- return +a == +b;
- // RegExps are compared by their source patterns and flags.
- case '[object RegExp]':
- return a.source == b.source &&
- a.global == b.global &&
- a.multiline == b.multiline &&
- a.ignoreCase == b.ignoreCase;
+ return +a === +b;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
@@ -895,17 +1011,29 @@
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
- if (aStack[length] == a) return bStack[length] == b;
+ if (aStack[length] === a) return bStack[length] === b;
+ }
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (
+ aCtor !== bCtor &&
+ // Handle Object.create(x) cases
+ 'constructor' in a && 'constructor' in b &&
+ !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+ _.isFunction(bCtor) && bCtor instanceof bCtor)
+ ) {
+ return false;
}
// Add the first object to the stack of traversed objects.
aStack.push(a);
bStack.push(b);
- var size = 0, result = true;
+ var size, result;
// Recursively compare objects and arrays.
- if (className == '[object Array]') {
+ if (className === '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
- result = size == b.length;
+ result = size === b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
@@ -913,29 +1041,18 @@
}
}
} else {
- // Objects with different constructors are not equivalent, but `Object`s
- // from different frames are.
- var aCtor = a.constructor, bCtor = b.constructor;
- if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
- _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
- return false;
- }
// Deep compare objects.
- for (var key in a) {
- if (_.has(a, key)) {
- // Count the expected number of properties.
- size++;
- // Deep compare each member.
+ var keys = _.keys(a), key;
+ size = keys.length;
+ // Ensure that both objects contain the same number of properties before comparing deep equality.
+ result = _.keys(b).length === size;
+ if (result) {
+ while (size--) {
+ // Deep compare each member
+ key = keys[size];
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
}
}
- // Ensure that both objects contain the same number of properties.
- if (result) {
- for (key in b) {
- if (_.has(b, key) && !(size--)) break;
- }
- result = !size;
- }
}
// Remove the first object from the stack of traversed objects.
aStack.pop();
@@ -952,7 +1069,7 @@
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (obj == null) return true;
- if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
@@ -965,18 +1082,19 @@
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
- return toString.call(obj) == '[object Array]';
+ return toString.call(obj) === '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
- return obj === Object(obj);
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
};
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
- each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) {
- return toString.call(obj) == '[object ' + name + ']';
+ return toString.call(obj) === '[object ' + name + ']';
};
});
@@ -984,14 +1102,14 @@
// there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
- return !!(obj && _.has(obj, 'callee'));
+ return _.has(obj, 'callee');
};
}
- // Optimize `isFunction` if appropriate.
- if (typeof (/./) !== 'function') {
+ // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
+ if (typeof /./ !== 'function') {
_.isFunction = function(obj) {
- return typeof obj === 'function';
+ return typeof obj == 'function' || false;
};
}
@@ -1002,12 +1120,12 @@
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) {
- return _.isNumber(obj) && obj != +obj;
+ return _.isNumber(obj) && obj !== +obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
- return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};
// Is a given value equal to null?
@@ -1023,7 +1141,7 @@
// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
_.has = function(obj, key) {
- return hasOwnProperty.call(obj, key);
+ return obj != null && hasOwnProperty.call(obj, key);
};
// Utility Functions
@@ -1036,15 +1154,45 @@
return this;
};
- // Keep the identity function around for default iterators.
+ // Keep the identity function around for default iteratees.
_.identity = function(value) {
return value;
};
+ // Predicate-generating functions. Often useful outside of Underscore.
+ _.constant = function(value) {
+ return function() {
+ return value;
+ };
+ };
+
+ _.noop = function(){};
+
+ _.property = function(key) {
+ return function(obj) {
+ return obj[key];
+ };
+ };
+
+ // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
+ _.matches = function(attrs) {
+ var pairs = _.pairs(attrs), length = pairs.length;
+ return function(obj) {
+ if (obj == null) return !length;
+ obj = new Object(obj);
+ for (var i = 0; i < length; i++) {
+ var pair = pairs[i], key = pair[0];
+ if (pair[1] !== obj[key] || !(key in obj)) return false;
+ }
+ return true;
+ };
+ };
+
// Run a function **n** times.
- _.times = function(n, iterator, context) {
- var accum = Array(n);
- for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+ _.times = function(n, iteratee, context) {
+ var accum = Array(Math.max(0, n));
+ iteratee = createCallback(iteratee, context, 1);
+ for (var i = 0; i < n; i++) accum[i] = iteratee(i);
return accum;
};
@@ -1057,53 +1205,45 @@
return min + Math.floor(Math.random() * (max - min + 1));
};
- // List of HTML entities for escaping.
- var entityMap = {
- escape: {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": ''',
- '/': '/'
- }
- };
- entityMap.unescape = _.invert(entityMap.escape);
-
- // Regexes containing the keys and values listed immediately above.
- var entityRegexes = {
- escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
- unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ // A (possibly faster) way to get the current timestamp as an integer.
+ _.now = Date.now || function() {
+ return new Date().getTime();
};
+ // List of HTML entities for escaping.
+ var escapeMap = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '`': '`'
+ };
+ var unescapeMap = _.invert(escapeMap);
+
// Functions for escaping and unescaping strings to/from HTML interpolation.
- _.each(['escape', 'unescape'], function(method) {
- _[method] = function(string) {
- if (string == null) return '';
- return ('' + string).replace(entityRegexes[method], function(match) {
- return entityMap[method][match];
- });
+ var createEscaper = function(map) {
+ var escaper = function(match) {
+ return map[match];
};
- });
+ // Regexes for identifying a key that needs to be escaped
+ var source = '(?:' + _.keys(map).join('|') + ')';
+ var testRegexp = RegExp(source);
+ var replaceRegexp = RegExp(source, 'g');
+ return function(string) {
+ string = string == null ? '' : '' + string;
+ return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+ };
+ };
+ _.escape = createEscaper(escapeMap);
+ _.unescape = createEscaper(unescapeMap);
// If the value of the named `property` is a function then invoke it with the
// `object` as context; otherwise, return it.
_.result = function(object, property) {
if (object == null) return void 0;
var value = object[property];
- return _.isFunction(value) ? value.call(object) : value;
- };
-
- // Add your own custom functions to the Underscore object.
- _.mixin = function(obj) {
- each(_.functions(obj), function(name){
- var func = _[name] = obj[name];
- _.prototype[name] = function() {
- var args = [this._wrapped];
- push.apply(args, arguments);
- return result.call(this, func.apply(_, args));
- };
- });
+ return _.isFunction(value) ? object[property]() : value;
};
// Generate a unique integer id (unique within the entire client session).
@@ -1134,22 +1274,26 @@
'\\': '\\',
'\r': 'r',
'\n': 'n',
- '\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
- var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+ var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+ var escapeChar = function(match) {
+ return '\\' + escapes[match];
+ };
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
- _.template = function(text, data, settings) {
- var render;
+ // NB: `oldSettings` only exists for backwards compatibility.
+ _.template = function(text, settings, oldSettings) {
+ if (!settings && oldSettings) settings = oldSettings;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
- var matcher = new RegExp([
+ var matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
@@ -1159,19 +1303,18 @@
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
- source += text.slice(index, offset)
- .replace(escaper, function(match) { return '\\' + escapes[match]; });
+ source += text.slice(index, offset).replace(escaper, escapeChar);
+ index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
- }
- if (interpolate) {
+ } else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
- }
- if (evaluate) {
+ } else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
- index = offset + match.length;
+
+ // Adobe VMs need the match returned to produce the correct offest.
return match;
});
source += "';\n";
@@ -1181,29 +1324,31 @@
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
- source + "return __p;\n";
+ source + 'return __p;\n';
try {
- render = new Function(settings.variable || 'obj', '_', source);
+ var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
- if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
- // Provide the compiled function source as a convenience for precompilation.
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+ // Provide the compiled source as a convenience for precompilation.
+ var argument = settings.variable || 'obj';
+ template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
- // Add a "chain" function, which will delegate to the wrapper.
+ // Add a "chain" function. Start chaining a wrapped Underscore object.
_.chain = function(obj) {
- return _(obj).chain();
+ var instance = _(obj);
+ instance._chain = true;
+ return instance;
};
// OOP
@@ -1217,41 +1362,55 @@
return this._chain ? _(obj).chain() : obj;
};
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ _.each(_.functions(obj), function(name) {
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result.call(this, func.apply(_, args));
+ };
+ });
+ };
+
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
- each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments);
- if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+ if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
return result.call(this, obj);
};
});
// Add all accessor Array functions to the wrapper.
- each(['concat', 'join', 'slice'], function(name) {
+ _.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments));
};
});
- _.extend(_.prototype, {
+ // Extracts the result from a wrapped and chained object.
+ _.prototype.value = function() {
+ return this._wrapped;
+ };
- // Start chaining a wrapped Underscore object.
- chain: function() {
- this._chain = true;
- return this;
- },
-
- // Extracts the result from a wrapped and chained object.
- value: function() {
- return this._wrapped;
- }
-
- });
-
-}).call(this);
+ // AMD registration happens at the end for compatibility with AMD loaders
+ // that may not enforce next-turn semantics on modules. Even though general
+ // practice for AMD registration is to be anonymous, underscore registers
+ // as a named module because, like jQuery, it is a base library that is
+ // popular enough to be bundled in a third party lib, but not be part of
+ // an AMD load request. Those cases could generate an error when an
+ // anonymous define() is called outside of a loader request.
+ if (typeof define === 'function' && define.amd) {
+ define('underscore', [], function() {
+ return _;
+ });
+ }
+}.call(this));
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
commit/galaxy-central: jmchilton: Merged in dannon/galaxy-central/stable (pull request #602)
by commits-noreply@bitbucket.org 10 Dec '14
by commits-noreply@bitbucket.org 10 Dec '14
10 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/212e1d5e9be5/
Changeset: 212e1d5e9be5
Branch: stable
User: jmchilton
Date: 2014-12-10 17:20:55+00:00
Summary: Merged in dannon/galaxy-central/stable (pull request #602)
[STABLE] Force sanitization of form.title and form.name. Header needs more digging; we actually use html content in the field.
Affected #: 1 file
diff -r 2db0fb9594d6c315e4e0a4be70f64373cfc708f6 -r 212e1d5e9be5a9a1e12c834bd545de504753c9fe templates/form.mako
--- a/templates/form.mako
+++ b/templates/form.mako
@@ -22,7 +22,7 @@
</%def>
-<%def name="title()">${form.title}</%def>
+<%def name="title()">${form.title | h}</%def><%def name="javascripts()">
${parent.javascripts()}
@@ -53,7 +53,7 @@
%endif
<div class="form" style="margin: 1em">
- <div class="form-title">${util.unicodify( form.title )}</div>
+ <div class="form-title">${util.unicodify( form.title ) | h }</div><div class="form-body"><%
has_file_input = False
@@ -62,7 +62,7 @@
has_file_input = True
break
%>
- <form name="${form.name}" action="${form.action}" method="post"
+ <form name="${form.name | h }" action="${form.action}" method="post"
%if has_file_input:
enctype="multipart/form-data"
%endif
@@ -76,28 +76,28 @@
<div class="${cls}">
%if input.use_label:
<label>
- ${_(input.label)}:
+ ${_(input.label) | h }:
</label>
%endif
<div class="form-row-input">
%if input.type == 'textarea':
- <textarea name="${input.name}">${input.value}</textarea>
+ <textarea name="${input.name | h }">${input.value | h }</textarea>
%elif input.type == 'select':
- <select name="${input.name}">
+ <select name="${input.name | h}">
%for (name, value) in input.options:
- <option value="${value}">${name}</option>
+ <option value="${value | h }">${name | h }</option>
%endfor
</select>
%else:
- <input type="${input.type}" name="${input.name}" value="${input.value}">
+ <input type="${input.type}" name="${input.name | h }" value="${input.value | h }">
%endif
</div>
%if input.error:
- <div class="form-row-error-message">${input.error}</div>
+ <div class="form-row-error-message">${input.error | h }</div>
%endif
%if input.help:
<div class="toolParamHelp" style="clear: both;">
- ${input.help}
+ ${input.help | h}
</div>
%endif
<div style="clear: both"></div>
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
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2d51d40f07ca/
Changeset: 2d51d40f07ca
Branch: stable
User: dannon
Date: 2014-12-09 19:46:03+00:00
Summary: Force sanitization of form.title and form.name. Header needs more digging; we actually use html content in the field.
Affected #: 1 file
diff -r fdc7863699d233e2b44d6d42f47dfaaa97e0d80e -r 2d51d40f07ca0d3ca266a33bf39190dfe8b9a6be templates/form.mako
--- a/templates/form.mako
+++ b/templates/form.mako
@@ -22,7 +22,7 @@
</%def>
-<%def name="title()">${form.title}</%def>
+<%def name="title()">${form.title | h}</%def><%def name="javascripts()">
${parent.javascripts()}
@@ -53,7 +53,7 @@
%endif
<div class="form" style="margin: 1em">
- <div class="form-title">${util.unicodify( form.title )}</div>
+ <div class="form-title">${util.unicodify( form.title ) | h}</div><div class="form-body"><%
has_file_input = False
@@ -62,7 +62,7 @@
has_file_input = True
break
%>
- <form name="${form.name}" action="${form.action}" method="post"
+ <form name="${form.name | h}" action="${form.action}" method="post"
%if has_file_input:
enctype="multipart/form-data"
%endif
https://bitbucket.org/galaxy/galaxy-central/commits/7adac1842adf/
Changeset: 7adac1842adf
Branch: stable
User: dannon
Date: 2014-12-09 20:44:08+00:00
Summary: Additionally sanitize form input fields (label, name, etc.)
Affected #: 1 file
diff -r 2d51d40f07ca0d3ca266a33bf39190dfe8b9a6be -r 7adac1842adf8db15183baff73e971aeab3537a9 templates/form.mako
--- a/templates/form.mako
+++ b/templates/form.mako
@@ -53,7 +53,7 @@
%endif
<div class="form" style="margin: 1em">
- <div class="form-title">${util.unicodify( form.title ) | h}</div>
+ <div class="form-title">${util.unicodify( form.title ) | h }</div><div class="form-body"><%
has_file_input = False
@@ -62,7 +62,7 @@
has_file_input = True
break
%>
- <form name="${form.name | h}" action="${form.action}" method="post"
+ <form name="${form.name | h }" action="${form.action}" method="post"
%if has_file_input:
enctype="multipart/form-data"
%endif
@@ -76,28 +76,28 @@
<div class="${cls}">
%if input.use_label:
<label>
- ${_(input.label)}:
+ ${_(input.label) | h }:
</label>
%endif
<div class="form-row-input">
%if input.type == 'textarea':
- <textarea name="${input.name}">${input.value}</textarea>
+ <textarea name="${input.name | h }">${input.value | h }</textarea>
%elif input.type == 'select':
- <select name="${input.name}">
+ <select name="${input.name | h}">
%for (name, value) in input.options:
- <option value="${value}">${name}</option>
+ <option value="${value | h }">${name | h }</option>
%endfor
</select>
%else:
- <input type="${input.type}" name="${input.name}" value="${input.value}">
+ <input type="${input.type}" name="${input.name | h }" value="${input.value | h }">
%endif
</div>
%if input.error:
- <div class="form-row-error-message">${input.error}</div>
+ <div class="form-row-error-message">${input.error | h }</div>
%endif
%if input.help:
<div class="toolParamHelp" style="clear: both;">
- ${input.help}
+ ${input.help | h}
</div>
%endif
<div style="clear: both"></div>
https://bitbucket.org/galaxy/galaxy-central/commits/212e1d5e9be5/
Changeset: 212e1d5e9be5
Branch: stable
User: jmchilton
Date: 2014-12-10 17:20:55+00:00
Summary: Merged in dannon/galaxy-central/stable (pull request #602)
[STABLE] Force sanitization of form.title and form.name. Header needs more digging; we actually use html content in the field.
Affected #: 1 file
diff -r 2db0fb9594d6c315e4e0a4be70f64373cfc708f6 -r 212e1d5e9be5a9a1e12c834bd545de504753c9fe templates/form.mako
--- a/templates/form.mako
+++ b/templates/form.mako
@@ -22,7 +22,7 @@
</%def>
-<%def name="title()">${form.title}</%def>
+<%def name="title()">${form.title | h}</%def><%def name="javascripts()">
${parent.javascripts()}
@@ -53,7 +53,7 @@
%endif
<div class="form" style="margin: 1em">
- <div class="form-title">${util.unicodify( form.title )}</div>
+ <div class="form-title">${util.unicodify( form.title ) | h }</div><div class="form-body"><%
has_file_input = False
@@ -62,7 +62,7 @@
has_file_input = True
break
%>
- <form name="${form.name}" action="${form.action}" method="post"
+ <form name="${form.name | h }" action="${form.action}" method="post"
%if has_file_input:
enctype="multipart/form-data"
%endif
@@ -76,28 +76,28 @@
<div class="${cls}">
%if input.use_label:
<label>
- ${_(input.label)}:
+ ${_(input.label) | h }:
</label>
%endif
<div class="form-row-input">
%if input.type == 'textarea':
- <textarea name="${input.name}">${input.value}</textarea>
+ <textarea name="${input.name | h }">${input.value | h }</textarea>
%elif input.type == 'select':
- <select name="${input.name}">
+ <select name="${input.name | h}">
%for (name, value) in input.options:
- <option value="${value}">${name}</option>
+ <option value="${value | h }">${name | h }</option>
%endfor
</select>
%else:
- <input type="${input.type}" name="${input.name}" value="${input.value}">
+ <input type="${input.type}" name="${input.name | h }" value="${input.value | h }">
%endif
</div>
%if input.error:
- <div class="form-row-error-message">${input.error}</div>
+ <div class="form-row-error-message">${input.error | h }</div>
%endif
%if input.help:
<div class="toolParamHelp" style="clear: both;">
- ${input.help}
+ ${input.help | h}
</div>
%endif
<div style="clear: both"></div>
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
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7eeb2a0f5a97/
Changeset: 7eeb2a0f5a97
User: jmchilton
Date: 2014-12-10 16:49:50+00:00
Summary: force_history_refresh does nothing so I am just setting it False in tool.
(TODO: Update planemo linter to mark this as a problem.)
Affected #: 1 file
diff -r 01a3ff6a376c4b95ca540bc120b5aab47920fd39 -r 7eeb2a0f5a977d88c506bf1365c8bb46b5897354 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1410,10 +1410,8 @@
self.version = "1.0.0"
# Support multi-byte tools
self.is_multi_byte = string_as_bool( root.get( "is_multi_byte", False ) )
- # Force history to fully refresh after job execution for this tool.
- # Useful i.e. when an indeterminate number of outputs are created by
- # a tool.
- self.force_history_refresh = string_as_bool( root.get( 'force_history_refresh', 'False' ) )
+ # Legacy feature, ignored by UI.
+ self.force_history_refresh = False
self.display_interface = string_as_bool( root.get( 'display_interface', str( self.display_interface ) ) )
self.require_login = string_as_bool( root.get( 'require_login', str( self.require_login ) ) )
# Load input translator, used by datasource tools to change names/values of incoming parameters
https://bitbucket.org/galaxy/galaxy-central/commits/05533cd3509f/
Changeset: 05533cd3509f
User: jmchilton
Date: 2014-12-10 16:49:50+00:00
Summary: Small PEP-8 fix for Tool.parse_inputs.
Affected #: 1 file
diff -r 7eeb2a0f5a977d88c506bf1365c8bb46b5897354 -r 05533cd3509f1e23f514fc5190467580824f0b37 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1580,11 +1580,11 @@
# template.
if self.nginx_upload and self.app.config.nginx_upload_path:
if '?' in urllib.unquote_plus( self.action ):
- raise Exception( 'URL parameters in a non-default tool action can not be used ' \
- 'in conjunction with nginx upload. Please convert them to ' \
+ raise Exception( 'URL parameters in a non-default tool action can not be used '
+ 'in conjunction with nginx upload. Please convert them to '
'hidden POST parameters' )
self.action = (self.app.config.nginx_upload_path + '?nginx_redir=',
- urllib.unquote_plus(self.action))
+ urllib.unquote_plus(self.action))
self.target = input_elem.get( "target", self.target )
self.method = input_elem.get( "method", self.method )
# Parse the actual parameters
https://bitbucket.org/galaxy/galaxy-central/commits/d66ad58a40e3/
Changeset: d66ad58a40e3
User: jmchilton
Date: 2014-12-10 16:49:50+00:00
Summary: Unit testing explicitly related to parsing of XML for parameters.
Cleans up some existing tests and does some de-duplication as well.
Affected #: 4 files
diff -r 05533cd3509f1e23f514fc5190467580824f0b37 -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 test/unit/tools/test_column_parameters.py
--- a/test/unit/tools/test_column_parameters.py
+++ b/test/unit/tools/test_column_parameters.py
@@ -2,16 +2,12 @@
test_select_parameters.py.
"""
-from unittest import TestCase
-from galaxy.tools.parameters import basic
from galaxy.util import bunch
from galaxy import model
-from elementtree.ElementTree import XML
+from .test_parameter_parsing import BaseParameterTestCase
-import tools_support
-
-class DataColumnParameterTestCase( TestCase, tools_support.UsesApp ):
+class DataColumnParameterTestCase( BaseParameterTestCase ):
def test_not_optional_by_default(self):
assert not self.__param_optional()
@@ -62,11 +58,7 @@
self.assertEqual( '2', self.param.get_initial_value( self.trans, { "input_tsv": self.build_ready_hda() } ) )
def setUp( self ):
- self.setup_app( mock_model=False )
- self.mock_tool = bunch.Bunch(
- app=self.app,
- tool_type="default",
- )
+ super(DataColumnParameterTestCase, self).setUp()
self.test_history = model.History()
self.app.model.context.add( self.test_history )
self.app.model.context.flush()
@@ -105,8 +97,8 @@
if self.set_data_ref:
data_ref_text = 'data_ref="input_tsv"'
template_xml = '''<param name="my_name" type="%s" %s %s %s %s></param>'''
- self.param_xml = XML( template_xml % ( self.type, data_ref_text, multi_text, optional_text, self.other_attributes ) )
- self._param = basic.ColumnListParameter( self.mock_tool, self.param_xml )
+ param_str = template_xml % ( self.type, data_ref_text, multi_text, optional_text, self.other_attributes )
+ self._param = self._parameter_for( xml=param_str )
self._param.ref_input = bunch.Bunch(formats=[model.datatypes_registry.get_datatype_by_extension("tabular")])
return self._param
diff -r 05533cd3509f1e23f514fc5190467580824f0b37 -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 test/unit/tools/test_data_parameters.py
--- a/test/unit/tools/test_data_parameters.py
+++ b/test/unit/tools/test_data_parameters.py
@@ -1,15 +1,10 @@
-from unittest import TestCase
-
from galaxy import model
from galaxy.util import bunch
-from galaxy.tools.parameters import basic
-from elementtree.ElementTree import XML
+from .test_parameter_parsing import BaseParameterTestCase
-import tools_support
-
-class DataToolParameterTestCase( TestCase, tools_support.UsesApp ):
+class DataToolParameterTestCase( BaseParameterTestCase ):
def test_to_python_none_values( self ):
assert None is self.param.to_python( None, self.app )
@@ -149,11 +144,7 @@
return hda
def setUp( self ):
- self.setup_app( mock_model=False )
- self.mock_tool = bunch.Bunch(
- app=self.app,
- tool_type="default",
- )
+ super(DataToolParameterTestCase, self).setUp()
self.test_history = model.History()
self.app.model.context.add( self.test_history )
self.app.model.context.flush()
@@ -187,8 +178,8 @@
if self.optional:
optional_text = 'optional="True"'
template_xml = '''<param name="data2" type="data" ext="txt" %s %s></param>'''
- self.param_xml = XML( template_xml % ( multi_text, optional_text ) )
- self._param = basic.DataToolParameter( self.mock_tool, self.param_xml )
+ param_str = template_xml % ( multi_text, optional_text )
+ self._param = self._parameter_for( tool=self.mock_tool, xml=param_str )
return self._param
diff -r 05533cd3509f1e23f514fc5190467580824f0b37 -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 test/unit/tools/test_parameter_parsing.py
--- /dev/null
+++ b/test/unit/tools/test_parameter_parsing.py
@@ -0,0 +1,283 @@
+from unittest import TestCase
+
+from galaxy.tools.parameters import basic
+from galaxy.util import bunch
+from elementtree.ElementTree import XML
+
+import tools_support
+
+
+class BaseParameterTestCase( TestCase, tools_support.UsesApp ):
+
+ def setUp(self):
+ self.setup_app( mock_model=False )
+ self.mock_tool = bunch.Bunch(
+ app=self.app,
+ tool_type="default",
+ )
+
+ def _parameter_for(self, **kwds):
+ content = kwds["xml"]
+ param_xml = XML( content )
+ return basic.ToolParameter.build( self.mock_tool, param_xml )
+
+
+class ParameterParsingTestCase( BaseParameterTestCase ):
+ """ Test the parsing of XML for most parameter types - in many
+ ways these are not very good tests since they break the abstraction
+ established by the tools. The docs tests in basic.py are better but
+ largely rely on HTML stuff we are moving to the client side so they
+ those tests may need to be updated anyway.
+
+ It occurs to me that rewriting this stuff to test to_dict would
+ be much better - since that is a public API of the the tools.
+ """
+
+ def test_parse_help_and_label(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault" label="x" help="y" />
+ """)
+ assert param.label == "x"
+ assert param.help == "y"
+
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault">
+ <label>x2</label>
+ <help>y2</help>
+ </param>
+ """)
+ assert param.label == "x2"
+ assert param.help == "y2"
+
+ def test_parse_sanitizers(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault">
+ <sanitizer invalid_char="">
+ <valid initial="string.digits"><add value=","/></valid>
+ </sanitizer>
+ </param>
+ """)
+ sanitizer = param.sanitizer
+ assert sanitizer is not None
+ assert sanitizer.sanitize_param("a") == ""
+ assert sanitizer.sanitize_param(",") == ","
+
+ def test_parse_optional(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault" />
+ """)
+ assert param.optional is False
+
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault" optional="true" />
+ """)
+ assert param.optional is True
+
+ def test_parse_validators(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault">
+ <validator type="unspecified_build" message="no genome?" />
+ </param>
+ """)
+ assert param.validators[0].message == "no genome?"
+
+ def test_text_params(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="texti" size="8" value="mydefault" />
+ """)
+ assert param.size == "8"
+ assert param.value == "mydefault"
+ assert param.type == "text"
+ assert not param.area
+
+ def test_text_area_params(self):
+ param = self._parameter_for(xml="""
+ <param type="text" name="textarea" area="true" />
+ """)
+ assert param.size is None
+ assert param.value is None
+ assert param.type == "text"
+ assert param.area
+
+ def test_integer_params(self):
+ param = self._parameter_for(xml="""
+ <param type="integer" name="intp" min="8" max="9" value="9" />
+ """)
+ assert param.name == "intp"
+ assert param.value == "9"
+ assert param.type == "integer"
+ param.validate( 8 )
+ self.assertRaises(Exception, lambda: param.validate( 10 ))
+
+ def test_float_params(self):
+ param = self._parameter_for(xml="""
+ <param type="float" name="floatp" min="7.8" max="9.5" value="9" />
+ """)
+ assert param.name == "floatp"
+ assert param.value == "9"
+ assert param.type == "float"
+ param.validate( 8.1 )
+ self.assertRaises(Exception, lambda: param.validate( 10.0 ))
+
+ def test_boolean_params(self):
+ param = self._parameter_for(xml="""
+ <param type="boolean" name="boolp" />
+ """)
+ assert param.name == "boolp"
+ assert param.truevalue == "true"
+ assert param.falsevalue == "false"
+ assert param.type == "boolean"
+
+ param = self._parameter_for(xml="""
+ <param type="boolean" name="boolp" truevalue="t" falsevalue="f" />
+ """)
+ assert param.truevalue == "t"
+ assert param.falsevalue == "f"
+
+ def test_file_params(self):
+ param = self._parameter_for(xml="""
+ <param type="file" name="filep" ajax-upload="true" />
+ """)
+ assert param.name == "filep"
+ assert param.type == "file"
+ assert param.ajax
+
+ def test_ftpfile_params(self):
+ param = self._parameter_for(xml="""
+ <param type="ftpfile" name="ftpfilep" />
+ """)
+ assert param.name == "ftpfilep"
+ assert param.type == "ftpfile"
+
+ def test_hidden(self):
+ param = self._parameter_for(xml="""
+ <param name="hiddenp" type="hidden" value="a hidden value" />
+ """)
+ assert param.name == "hiddenp"
+ assert param.type == "hidden"
+ assert param.value == "a hidden value"
+
+ def test_base_url(self):
+ param = self._parameter_for(xml="""
+ <param name="urlp" type="baseurl" value="http://twitter.com/" />
+ """)
+ assert param.name == "urlp"
+ assert param.type == "baseurl"
+ assert param.value == "http://twitter.com/"
+
+ param = self._parameter_for(xml="""
+ <param name="urlp" type="baseurl" />
+ """)
+ assert param.value == ""
+
+ def test_select_static(self):
+ param = self._parameter_for(xml="""
+ <param name="selectp" type="select" multiple="true">
+ <option value="a">A</option>
+ <option value="b" selected="true">B</option>
+ </param>
+ """)
+ assert param.multiple is True
+ assert param.name == "selectp"
+ assert param.type == "select"
+
+ assert param.options is None
+ assert param.dynamic_options is None
+ assert not param.is_dynamic
+
+ options = param.static_options
+ assert options[0][0] == "A"
+ assert options[0][1] == "a"
+ assert not options[0][2]
+
+ assert options[1][0] == "B"
+ assert options[1][1] == "b"
+ assert options[1][2]
+
+ def test_select_dynamic(self):
+ param = self._parameter_for(xml="""
+ <param name="selectp" type="select" dynamic_options="cow">
+ </param>
+ """)
+ assert param.multiple is False
+ assert param.options is None
+ assert param.dynamic_options == "cow"
+ ## This should be None or something - not undefined.
+ # assert not param.static_options
+ assert param.is_dynamic
+
+ def test_select_options_from(self):
+ param = self._parameter_for(xml="""
+ <param name="selectp" type="select">
+ <options from_data_table="cow">
+ </options>
+ </param>
+ """)
+ assert param.dynamic_options is None
+ assert param.is_dynamic
+
+ # More detailed tests of dynamic options should be placed
+ # in test_select_parameters.
+ assert param.options.missing_tool_data_table_name == "cow"
+
+ def test_genome_build(self):
+ param = self._parameter_for(xml="""
+ <param name="genomep" type="genomebuild">
+ </param>
+ """)
+ assert param.type == "genomebuild"
+ assert param.name == "genomep"
+ assert param.static_options
+
+ # Column and Data have sufficient test coverage in
+ # other modules.
+ def test_drilldown(self):
+ param = self._parameter_for(xml="""
+ <param name="some_name" type="drill_down" display="checkbox" hierarchy="recurse" multiple="true">
+ <options>
+ <option name="Heading 1" value="heading1">
+ <option name="Option 1" value="option1"/>
+ <option name="Option 2" value="option2"/>
+ <option name="Heading 1" value="heading1">
+ <option name="Option 3" value="option3"/>
+ <option name="Option 4" value="option4"/>
+ </option>
+ </option>
+ <option name="Option 5" value="option5"/>
+ </options>
+ </param>
+ """)
+ assert param.type == "drill_down"
+ assert param.name == "some_name"
+ assert param.options
+
+ heading1 = param.options[0]
+ assert heading1["selected"] is False
+ assert heading1["name"] == "Heading 1"
+ assert heading1["value"] == "heading1"
+ option1 = heading1["options"][0]
+ assert option1["selected"] is False
+ assert option1["name"] == "Option 1"
+ assert option1["value"] == "option1"
+
+ option5 = param.options[1]
+ assert option5["selected"] is False
+ assert option5["name"] == "Option 5"
+ assert option5["value"] == "option5"
+ assert len(option5["options"]) == 0
+
+ def test_tool_collection(self):
+ param = self._parameter_for(xml="""
+ <param name="datac" type="data_collection" collection_type="list" format="txt">
+ </param>
+ """)
+ assert param.type == "data_collection"
+ assert param.collection_type == "list"
+
+ def test_library(self):
+ param = self._parameter_for(xml="""
+ <param name="libraryp" type="library_data">
+ </param>
+ """)
+ assert param.type == "library_data"
+ assert param.name == "libraryp"
diff -r 05533cd3509f1e23f514fc5190467580824f0b37 -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 test/unit/tools/test_select_parameters.py
--- a/test/unit/tools/test_select_parameters.py
+++ b/test/unit/tools/test_select_parameters.py
@@ -1,13 +1,11 @@
-from unittest import TestCase
from galaxy.util import bunch
from galaxy import model
from galaxy.tools.parameters import basic
-from elementtree.ElementTree import XML
-import tools_support
+from .test_parameter_parsing import BaseParameterTestCase
-class SelectToolParameterTestCase( TestCase, tools_support.UsesApp ):
+class SelectToolParameterTestCase( BaseParameterTestCase ):
def test_dep_dummy_datasets_need_late_validation( self ):
self.options_xml = '''<options><filter type="data_meta" ref="input_bam" key="dbkey"/></options>'''
@@ -51,11 +49,7 @@
# TODO: Good deal of overlap here with DataToolParameterTestCase,
# refactor.
def setUp( self ):
- self.setup_app( mock_model=False )
- self.mock_tool = bunch.Bunch(
- app=self.app,
- tool_type="default",
- )
+ super(SelectToolParameterTestCase, self).setUp()
self.test_history = model.History()
self.app.model.context.add( self.test_history )
self.app.model.context.flush()
@@ -88,8 +82,8 @@
if self.set_data_ref:
data_ref_text = 'data_ref="input_bam"'
template_xml = '''<param name="my_name" type="%s" %s %s %s>%s</param>'''
- self.param_xml = XML( template_xml % ( self.type, data_ref_text, multi_text, optional_text, options_text ) )
- self._param = basic.SelectToolParameter( self.mock_tool, self.param_xml )
+ param_str = template_xml % ( self.type, data_ref_text, multi_text, optional_text, options_text )
+ self._param = self._parameter_for( xml=param_str )
return self._param
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