root/galaxy-central/lib/galaxy/tools/parameters/basic.py @ 2

リビジョン 2, 63.5 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1"""
2Basic tool parameters.
3"""
4
5import logging, string, sys, os, os.path
6from elementtree.ElementTree import XML, Element
7from galaxy import config, datatypes, util
8from galaxy.web import form_builder
9from galaxy.util.bunch import Bunch
10from galaxy.util import string_as_bool, sanitize_param
11from sanitize import ToolParameterSanitizer
12import validation, dynamic_options
13# For BaseURLToolParameter
14from galaxy.web import url_for
15import galaxy.model
16
17log = logging.getLogger(__name__)
18
19class ToolParameter( object ):
20    """
21    Describes a parameter accepted by a tool. This is just a simple stub at the
22    moment but in the future should encapsulate more complex parameters (lists
23    of valid choices, validation logic, ...)
24    """
25    def __init__( self, tool, param, context=None ):
26        self.tool = tool
27        self.refresh_on_change = False
28        self.refresh_on_change_values = []
29        self.name = param.get("name")
30        self.type = param.get("type")
31        self.label = util.xml_text(param, "label")
32        self.help = util.xml_text(param, "help")
33        self.sanitizer = param.find( "sanitizer" )
34        if self.sanitizer is not None:
35            self.sanitizer = ToolParameterSanitizer.from_element( self.sanitizer )
36        self.html = "no html set"
37        self.repeat = param.get("repeat", None)
38        self.condition = param.get( "condition", None )
39        self.validators = []
40        for elem in param.findall("validator"):
41            self.validators.append( validation.Validator.from_element( self, elem ) )
42
43    def get_label( self ):
44        """Return user friendly name for the parameter"""
45        if self.label: return self.label
46        else: return self.name
47
48    def get_html_field( self, trans=None, value=None, other_values={} ):
49        raise TypeError( "Abstract Method" )
50
51    def get_html( self, trans=None, value=None, other_values={}):
52        """
53        Returns the html widget corresponding to the parameter.
54        Optionally attempt to retain the current value specific by 'value'
55        """
56        return self.get_html_field( trans, value, other_values ).get_html()
57       
58    def from_html( self, value, trans=None, other_values={} ):
59        """
60        Convert a value from an HTML POST into the parameters preferred value
61        format.
62        """
63        return value
64       
65    def get_initial_value( self, trans, context ):
66        """
67        Return the starting value of the parameter
68        """
69        return None
70
71    def get_required_enctype( self ):
72        """
73        If this parameter needs the form to have a specific encoding
74        return it, otherwise return None (indicating compatibility with
75        any encoding)
76        """
77        return None
78       
79    def get_dependencies( self ):
80        """
81        Return the names of any other parameters this parameter depends on
82        """
83        return []
84       
85    def filter_value( self, value, trans=None, other_values={} ):
86        """
87        Parse the value returned by the view into a form usable by the tool OR
88        raise a ValueError.
89        """
90        return value
91       
92    def to_string( self, value, app ):
93        """Convert a value to a string representation suitable for persisting"""
94        return str( value )
95   
96    def to_python( self, value, app ):
97        """Convert a value created with to_string back to an object representation"""
98        return value
99       
100    def value_to_basic( self, value, app ):
101        if isinstance( value, RuntimeValue ):
102            return { "__class__": "RuntimeValue" }
103        return self.to_string( value, app )
104       
105    def value_from_basic( self, value, app, ignore_errors=False ):
106        # HACK: Some things don't deal with unicode well, psycopg problem?
107        if type( value ) == unicode:
108            value = str( value )
109        # Handle Runtime values (valid for any parameter?)
110        if isinstance( value, dict ) and '__class__' in value and value['__class__'] == "RuntimeValue":
111            return RuntimeValue()
112        # Delegate to the 'to_python' method
113        if ignore_errors:
114            try:
115                return self.to_python( value, app )
116            except:
117                return value
118        else:
119            return self.to_python( value, app )
120           
121    def value_to_display_text( self, value, app ):
122        """
123        Convert a value to a text representation suitable for displaying to
124        the user
125        """
126        return value
127       
128    def to_param_dict_string( self, value, other_values={} ):
129        value = str( value )
130        if self.tool is None or self.tool.options.sanitize:
131            if self.sanitizer:
132                value = self.sanitizer.sanitize_param( value )
133            else:
134                value = sanitize_param( value )
135        return value
136       
137    def validate( self, value, history=None ):
138        for validator in self.validators:
139            validator.validate( value, history )
140
141    @classmethod
142    def build( cls, tool, param ):
143        """Factory method to create parameter of correct type"""
144        param_type = param.get("type")
145        if not param_type or param_type not in parameter_types:
146            raise ValueError( "Unknown tool parameter type '%s'" % param_type )
147        else:
148            return parameter_types[param_type]( tool, param )
149       
150class TextToolParameter( ToolParameter ):
151    """
152    Parameter that can take on any text value.
153   
154    >>> p = TextToolParameter( None, XML( '<param name="blah" type="text" size="4" value="default" />' ) )
155    >>> print p.name
156    blah
157    >>> print p.get_html()
158    <input type="text" name="blah" size="4" value="default">
159    >>> print p.get_html( value="meh" )
160    <input type="text" name="blah" size="4" value="meh">
161    """
162    def __init__( self, tool, elem ):
163        ToolParameter.__init__( self, tool, elem )
164        self.size = elem.get( 'size' )
165        self.value = elem.get( 'value' )
166        self.area = string_as_bool( elem.get( 'area', False ) )
167    def get_html_field( self, trans=None, value=None, other_values={} ):
168        if self.area:
169            return form_builder.TextArea( self.name, self.size, value or self.value )
170        else:
171            return form_builder.TextField( self.name, self.size, value or self.value )
172    def get_initial_value( self, trans, context ):
173        return self.value
174
175class IntegerToolParameter( TextToolParameter ):
176    """
177    Parameter that takes an integer value.
178   
179    >>> p = IntegerToolParameter( None, XML( '<param name="blah" type="integer" size="4" value="10" />' ) )
180    >>> print p.name
181    blah
182    >>> print p.get_html()
183    <input type="text" name="blah" size="4" value="10">
184    >>> type( p.from_html( "10" ) )
185    <type 'int'>
186    >>> type( p.from_html( "bleh" ) )
187    Traceback (most recent call last):
188        ...
189    ValueError: An integer is required
190    """
191    def __init__( self, tool, elem ):
192        TextToolParameter.__init__( self, tool, elem )
193        if self.value:
194            try:
195                int( self.value )
196            except:
197                raise ValueError( "An integer is required" )
198        elif self.value is None:
199            raise ValueError( "The settings for this field require a 'value' setting and optionally a default value which must be an integer" )
200    def get_html_field( self, trans=None, value=None, other_values={} ):
201        if isinstance( value, int ):
202            value = str( value )
203        return super( IntegerToolParameter, self ).get_html_field( trans=trans, value=value, other_values=other_values )
204    def from_html( self, value, trans=None, other_values={} ):
205        try:
206            return int( value )
207        except:
208            raise ValueError( "An integer is required" )
209    def to_python( self, value, app ):
210        return int( value )
211    def get_initial_value( self, trans, context ):
212        if self.value:
213            return int( self.value )
214        else:
215            return None
216           
217class FloatToolParameter( TextToolParameter ):
218    """
219    Parameter that takes a real number value.
220   
221    >>> p = FloatToolParameter( None, XML( '<param name="blah" type="float" size="4" value="3.141592" />' ) )
222    >>> print p.name
223    blah
224    >>> print p.get_html()
225    <input type="text" name="blah" size="4" value="3.141592">
226    >>> type( p.from_html( "36.1" ) )
227    <type 'float'>
228    >>> type( p.from_html( "bleh" ) )
229    Traceback (most recent call last):
230        ...
231    ValueError: A real number is required
232    """
233    def __init__( self, tool, elem ):
234        TextToolParameter.__init__( self, tool, elem )
235        if self.value:
236            try:
237                float( self.value )
238            except:
239                raise ValueError( "A real number is required" )
240        elif self.value is None:
241            raise ValueError( "The settings for this field require a 'value' setting and optionally a default value which must be a real number" )
242    def get_html_field( self, trans=None, value=None, other_values={} ):
243        if isinstance( value, float ):
244            value = str( value )
245        return super( FloatToolParameter, self ).get_html_field( trans=trans, value=value, other_values=other_values )
246    def from_html( self, value, trans=None, other_values={} ):
247        try:
248            return float( value )
249        except:
250            raise ValueError( "A real number is required" )
251    def to_python( self, value, app ):
252        return float( value )
253    def get_initial_value( self, trans, context ):
254        try:
255            return float( self.value )
256        except:
257            return None
258
259class BooleanToolParameter( ToolParameter ):
260    """
261    Parameter that takes one of two values.
262   
263    >>> p = BooleanToolParameter( None, XML( '<param name="blah" type="boolean" checked="yes" truevalue="bulletproof vests" falsevalue="cellophane chests" />' ) )
264    >>> print p.name
265    blah
266    >>> print p.get_html()
267    <input type="checkbox" name="blah" value="true" checked><input type="hidden" name="blah" value="true">
268    >>> print p.from_html( ["true","true"] )
269    True
270    >>> print p.to_param_dict_string( True )
271    bulletproof vests
272    >>> print p.from_html( ["true"] )
273    False
274    >>> print p.to_param_dict_string( False )
275    cellophane chests
276    """
277    def __init__( self, tool, elem ):
278        ToolParameter.__init__( self, tool, elem )
279        self.truevalue = elem.get( 'truevalue', 'true' )
280        self.falsevalue = elem.get( 'falsevalue', 'false' )
281        self.checked = string_as_bool( elem.get( 'checked' ) )
282    def get_html_field( self, trans=None, value=None, other_values={} ):
283        checked = self.checked
284        if value:
285            checked = form_builder.CheckboxField.is_checked( value )
286        return form_builder.CheckboxField( self.name, checked )
287    def from_html( self, value, trans=None, other_values={} ):
288        return form_builder.CheckboxField.is_checked( value ) 
289    def to_python( self, value, app ):
290        return ( value == 'True' )
291    def get_initial_value( self, trans, context ):
292        return self.checked
293    def to_param_dict_string( self, value, other_values={} ):
294        if value:
295            return self.truevalue
296        else:
297            return self.falsevalue
298
299class FileToolParameter( ToolParameter ):
300    """
301    Parameter that takes an uploaded file as a value.
302   
303    >>> p = FileToolParameter( None, XML( '<param name="blah" type="file"/>' ) )
304    >>> print p.name
305    blah
306    >>> print p.get_html()
307    <input type="file" name="blah">
308    >>> p = FileToolParameter( None, XML( '<param name="blah" type="file" ajax-upload="true"/>' ) )
309    >>> print p.get_html()
310    <input type="file" name="blah" galaxy-ajax-upload="true">
311    """
312    def __init__( self, tool, elem ):
313        """
314        Example: C{<param name="bins" type="file" />}
315        """
316        ToolParameter.__init__( self, tool, elem )
317        self.ajax = string_as_bool( elem.get( 'ajax-upload' ) )
318    def get_html_field( self, trans=None, value=None, other_values={}  ):
319        return form_builder.FileField( self.name, ajax = self.ajax, value = value )
320    def from_html( self, value, trans=None, other_values={} ):
321        # Middleware or proxies may encode files in special ways (TODO: this
322        # should be pluggable)
323        if type( value ) == dict:
324            upload_store = self.tool.app.config.nginx_upload_store
325            assert upload_store, \
326                "Request appears to have been processed by nginx_upload_module \
327                but Galaxy is not configured to recognize it"
328            # Check that the file is in the right location
329            local_filename = os.path.abspath( value['path'] )
330            assert local_filename.startswith( upload_store ), \
331                "Filename provided by nginx is not in correct directory"
332            value = dict(
333                filename = value["name"],
334                local_filename = local_filename
335            )
336        return value
337    def get_required_enctype( self ):
338        """
339        File upload elements require the multipart/form-data encoding
340        """
341        return "multipart/form-data"
342    def to_string( self, value, app ):
343        if value in [ None, '' ]:
344            return None
345        elif isinstance( value, unicode ) or isinstance( value, str ):
346            return value
347        elif isinstance( value, dict ):
348            # or should we jsonify?
349            try:
350                return value['local_filename']
351            except:
352                return None
353        raise Exception( "FileToolParameter cannot be persisted" )
354    def to_python( self, value, app ):
355        if value is None:
356            return None
357        elif isinstance( value, unicode ) or isinstance( value, str ):
358            return value
359        else:
360            raise Exception( "FileToolParameter cannot be persisted" )
361    def get_initial_value( self, trans, context ):
362        return None
363       
364class HiddenToolParameter( ToolParameter ):
365    """
366    Parameter that takes one of two values.
367   
368    FIXME: This seems hacky, parameters should only describe things the user
369           might change. It is used for 'initializing' the UCSC proxy tool
370   
371    >>> p = HiddenToolParameter( None, XML( '<param name="blah" type="hidden" value="wax so rockin"/>' ) )
372    >>> print p.name
373    blah
374    >>> print p.get_html()
375    <input type="hidden" name="blah" value="wax so rockin">
376    """   
377    def __init__( self, tool, elem ):
378        ToolParameter.__init__( self, tool, elem )
379        self.value = elem.get( 'value' )
380    def get_html_field( self, trans=None, value=None, other_values={} ):
381        return form_builder.HiddenField( self.name, self.value )
382    def get_initial_value( self, trans, context ):
383        return self.value
384    def get_label( self ):
385        return None
386   
387## This is clearly a HACK, parameters should only be used for things the user
388## can change, there needs to be a different way to specify this. I'm leaving
389## it for now to avoid breaking any tools.
390
391class BaseURLToolParameter( ToolParameter ):
392    """
393    Returns a parameter the contains its value prepended by the
394    current server base url. Used in all redirects.
395    """
396    def __init__( self, tool, elem ):
397        ToolParameter.__init__( self, tool, elem )
398        self.value = elem.get( 'value', '' )
399    def get_value( self, trans ):
400        # url = trans.request.base + self.value
401        url = url_for( self.value, qualified=True )
402        return url
403    def get_html_field( self, trans=None, value=None, other_values={} ):
404        return form_builder.HiddenField( self.name, self.get_value( trans ) )
405    def get_initial_value( self, trans, context ):
406        return self.value
407    def get_label( self ):
408        # BaseURLToolParameters are ultimately "hidden" parameters
409        return None
410
411class SelectToolParameter( ToolParameter ):
412    """
413    Parameter that takes on one (or many) or a specific set of values.
414   
415    >>> p = SelectToolParameter( None, XML(
416    ... '''
417    ... <param name="blah" type="select">
418    ...     <option value="x">I am X</option>
419    ...     <option value="y" selected="true">I am Y</option>
420    ...     <option value="z">I am Z</option>
421    ... </param>
422    ... ''' ) )
423    >>> print p.name
424    blah
425    >>> print p.get_html()
426    <select name="blah" last_selected_value="y">
427    <option value="x">I am X</option>
428    <option value="y" selected>I am Y</option>
429    <option value="z">I am Z</option>
430    </select>
431    >>> print p.get_html( value="z" )
432    <select name="blah" last_selected_value="z">
433    <option value="x">I am X</option>
434    <option value="y">I am Y</option>
435    <option value="z" selected>I am Z</option>
436    </select>
437    >>> print p.filter_value( "y" )
438    y
439
440    >>> p = SelectToolParameter( None, XML(
441    ... '''
442    ... <param name="blah" type="select" multiple="true">
443    ...     <option value="x">I am X</option>
444    ...     <option value="y" selected="true">I am Y</option>
445    ...     <option value="z" selected="true">I am Z</option>
446    ... </param>
447    ... ''' ) )
448    >>> print p.name
449    blah
450    >>> print p.get_html()
451    <select name="blah" multiple last_selected_value="z">
452    <option value="x">I am X</option>
453    <option value="y" selected>I am Y</option>
454    <option value="z" selected>I am Z</option>
455    </select>
456    >>> print p.get_html( value=["x","y"])
457    <select name="blah" multiple last_selected_value="y">
458    <option value="x" selected>I am X</option>
459    <option value="y" selected>I am Y</option>
460    <option value="z">I am Z</option>
461    </select>
462    >>> print p.to_param_dict_string( ["y", "z"] )
463    y,z
464   
465    >>> p = SelectToolParameter( None, XML(
466    ... '''
467    ... <param name="blah" type="select" multiple="true" display="checkboxes">
468    ...     <option value="x">I am X</option>
469    ...     <option value="y" selected="true">I am Y</option>
470    ...     <option value="z" selected="true">I am Z</option>
471    ... </param>
472    ... ''' ) )
473    >>> print p.name
474    blah
475    >>> print p.get_html()
476    <div class="checkUncheckAllPlaceholder" checkbox_name="blah"></div>
477    <div><input type="checkbox" name="blah" value="x">I am X</div>
478    <div class="odd_row"><input type="checkbox" name="blah" value="y" checked>I am Y</div>
479    <div><input type="checkbox" name="blah" value="z" checked>I am Z</div>
480    >>> print p.get_html( value=["x","y"])
481    <div class="checkUncheckAllPlaceholder" checkbox_name="blah"></div>
482    <div><input type="checkbox" name="blah" value="x" checked>I am X</div>
483    <div class="odd_row"><input type="checkbox" name="blah" value="y" checked>I am Y</div>
484    <div><input type="checkbox" name="blah" value="z">I am Z</div>
485    >>> print p.to_param_dict_string( ["y", "z"] )
486    y,z
487    """
488    def __init__( self, tool, elem, context=None ):
489        ToolParameter.__init__( self, tool, elem )
490        self.multiple = string_as_bool( elem.get( 'multiple', False ) )
491        self.display = elem.get( 'display', None )
492        self.separator = elem.get( 'separator', ',' )
493        self.legal_values = set()
494        # TODO: the <dynamic_options> tag is deprecated and should be replaced with the <options> tag.
495        self.dynamic_options = elem.get( "dynamic_options", None )
496        options = elem.find( 'options' )
497        if options is None:
498            self.options = None
499        else:
500            self.options = dynamic_options.DynamicOptions( options, self )
501            for validator in self.options.validators:
502                self.validators.append( validator )
503        if self.dynamic_options is None and self.options is None:
504            self.static_options = list()
505            for index, option in enumerate( elem.findall( "option" ) ):
506                value = option.get( "value" )
507                self.legal_values.add( value )
508                selected = string_as_bool( option.get( "selected", False ) )
509                self.static_options.append( ( option.text, value, selected ) )
510        self.is_dynamic = ( ( self.dynamic_options is not None ) or ( self.options is not None ) )
511    def get_options( self, trans, other_values ):
512        if self.options:
513            return self.options.get_options( trans, other_values )
514        elif self.dynamic_options:
515            return eval( self.dynamic_options, self.tool.code_namespace, other_values )
516        else:
517            return self.static_options
518    def get_legal_values( self, trans, other_values ):
519        def _get_UnvalidatedValue_value( value ):
520            if isinstance( value, UnvalidatedValue ):
521                return value.value
522            return value
523        if self.options:
524            return map( _get_UnvalidatedValue_value, set( v for _, v, _ in self.options.get_options( trans, other_values ) ) )
525        elif self.dynamic_options:
526            return set( v for _, v, _ in eval( self.dynamic_options, self.tool.code_namespace, other_values ) )
527        else:
528            return self.legal_values   
529    def get_html_field( self, trans=None, value=None, context={} ):
530        # Dynamic options are not yet supported in workflow, allow
531        # specifying the value as text for now.
532        if self.need_late_validation( trans, context ):
533            assert isinstance( value, UnvalidatedValue )
534            value = value.value
535            if self.multiple:
536                if value is None:
537                    value = ""
538                else:
539                    value = "\n".join( value )
540                return form_builder.TextArea( self.name, value=value )
541            else:
542                return form_builder.TextField( self.name, value=(value or "") )
543        if value is not None:
544            if not isinstance( value, list ): value = [ value ]
545        field = form_builder.SelectField( self.name, self.multiple, self.display, self.refresh_on_change, refresh_on_change_values = self.refresh_on_change_values )
546        options = self.get_options( trans, context )
547        for text, optval, selected in options:
548            if isinstance( optval, UnvalidatedValue ):
549                optval = optval.value
550                text = "%s (unvalidated)" % text
551            if value:
552                selected = ( optval in value )
553            field.add_option( text, optval, selected )
554        return field
555    def from_html( self, value, trans=None, context={} ):
556        if self.need_late_validation( trans, context ):
557            if self.multiple:
558                # While it is generally allowed that a select value can be '',
559                # we do not allow this to be the case in a dynamically
560                # generated multiple select list being set in workflow building
561                # mode we instead treat '' as 'No option Selected' (None)
562                if value == '':
563                    value = None
564                else:
565                    if not isinstance( value, list ):
566                        value = value.split( "\n" )
567            return UnvalidatedValue( value )
568        legal_values = self.get_legal_values( trans, context )
569        if isinstance( value, list ):
570            if not(self.repeat):
571                assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
572            rval = []
573            for v in value:
574                v = util.restore_text( v )
575                if v not in legal_values:
576                    raise ValueError( "An invalid option was selected, please verify" )
577                rval.append( v )
578            return rval
579        else:
580            value = util.restore_text( value )
581            if value not in legal_values:
582                raise ValueError( "An invalid option was selected, please verify" )
583            return value   
584    def to_param_dict_string( self, value, other_values={} ):
585        if value is None:
586            return "None"
587        if isinstance( value, list ):
588            if not( self.repeat ):
589                assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
590            value = map( str, value )
591        else:
592            value = str( value )
593        if self.tool is None or self.tool.options.sanitize:
594            if self.sanitizer:
595                value = self.sanitizer.sanitize_param( value )
596            else:
597                value = sanitize_param( value )
598        if isinstance( value, list ):
599            value = self.separator.join( value )
600        return value
601    def value_to_basic( self, value, app ):
602        if isinstance( value, UnvalidatedValue ):
603            return { "__class__": "UnvalidatedValue", "value": value.value }
604        elif isinstance( value, RuntimeValue ):
605            # Need to handle runtime value's ourself since delegating to the
606            # parent method causes the value to be turned into a string, which
607            # breaks multiple selection
608            return { "__class__": "RuntimeValue" }
609        return value
610    def value_from_basic( self, value, app, ignore_errors=False ):
611        if isinstance( value, dict ) and value["__class__"] == "UnvalidatedValue":
612            return UnvalidatedValue( value["value"] )
613        return super( SelectToolParameter, self ).value_from_basic( value, app )
614    def need_late_validation( self, trans, context ):
615        """
616        Determine whether we need to wait to validate this parameters value
617        given the current state. For parameters with static options this is
618        always false (can always validate immediately). For parameters with
619        dynamic options, we need to check whether the other parameters which
620        determine what options are valid have been set. For the old style
621        dynamic options which do not specify dependencies, this is always true
622        (must valiate at runtime).
623        """
624        # Option list is statically defined, never need late validation
625        if not self.is_dynamic:
626            return False
627        # Old style dynamic options, no dependency information so there isn't
628        # a lot we can do: if we're dealing with workflows, have to assume
629        # late validation no matter what.
630        if self.dynamic_options is not None:
631            return ( trans is None or trans.workflow_building_mode )
632        # If we got this far, we can actually look at the dependencies
633        # to see if their values will not be available until runtime.
634        for dep_name in self.get_dependencies():
635            dep_value = context[ dep_name ]
636            # Dependency on a dataset that does not yet exist
637            if isinstance( dep_value, DummyDataset ):
638                return True
639            # Dependency on a value that has not been checked
640            if isinstance( dep_value, UnvalidatedValue ):
641                return True
642            # Dependency on a value that does not yet exist
643            if isinstance( dep_value, RuntimeValue ):
644                return True
645        # Dynamic, but all dependenceis are known and have values
646        return False
647    def get_initial_value( self, trans, context ):
648        # More working around dynamic options for workflow
649        if self.need_late_validation( trans, context ):
650            # Really the best we can do?
651            return UnvalidatedValue( None )
652        options = list( self.get_options( trans, context ) )
653        value = [ optval for _, optval, selected in options if selected ]
654        if len( value ) == 0:
655            if not self.multiple and options:
656                # Nothing selected, but not a multiple select, with some values,
657                # so we have to default to something (the HTML form will anyway)
658                # TODO: deal with optional parameters in a better way
659                value = options[0][1]
660            else:
661                value = None
662        elif len( value ) == 1:
663            value = value[0]
664        return value
665    def value_to_display_text( self, value, app ):
666        if isinstance( value, UnvalidatedValue ):
667            suffix = "\n(value not yet validated)"
668            value = value.value
669        else:
670            suffix = ""
671        if not isinstance( value, list ):
672            value = [ value ]
673        # FIXME: Currently only translating values back to labels if they
674        #        are not dynamic
675        if self.is_dynamic:
676            rval = map( str, value )
677        else:
678            options = list( self.static_options )
679            rval = []
680            for t, v, s in options:
681                if v in value:
682                    rval.append( t )
683        return "\n".join( rval ) + suffix   
684    def get_dependencies( self ):
685        """
686        Get the *names* of the other params this param depends on.
687        """
688        if self.options:
689            return self.options.get_dependency_names()
690        else:
691            return []
692
693class GenomeBuildParameter( SelectToolParameter ):
694    """
695    Select list that sets the last used genome build for the current history
696    as "selected".
697   
698    >>> # Create a mock transcation with 'hg17' as the current build
699    >>> from galaxy.util.bunch import Bunch
700    >>> trans = Bunch( history=Bunch( genome_build='hg17' ), db_builds=util.dbnames )
701   
702    >>> p = GenomeBuildParameter( None, XML(
703    ... '''
704    ... <param name="blah" type="genomebuild" />
705    ... ''' ) )
706    >>> print p.name
707    blah
708   
709    >>> # hg17 should be selected by default
710    >>> print p.get_html( trans ) # doctest: +ELLIPSIS
711    <select name="blah" last_selected_value="hg17">
712    <option value="?">unspecified (?)</option>
713    ...
714    <option value="hg18">Human Mar. 2006 (NCBI36/hg18) (hg18)</option>
715    <option value="hg17" selected>Human May 2004 (NCBI35/hg17) (hg17)</option>
716    ...
717    </select>
718   
719    >>> # If the user selected something else already, that should be used
720    >>> # instead
721    >>> print p.get_html( trans, value='hg18' ) # doctest: +ELLIPSIS
722    <select name="blah" last_selected_value="hg18">
723    <option value="?">unspecified (?)</option>
724    ...
725    <option value="hg18" selected>Human Mar. 2006 (NCBI36/hg18) (hg18)</option>
726    <option value="hg17">Human May 2004 (NCBI35/hg17) (hg17)</option>
727    ...
728    </select>
729   
730    >>> print p.filter_value( "hg17" )
731    hg17
732    """
733    def get_options( self, trans, other_values ):
734        last_used_build = trans.history.genome_build
735        for dbkey, build_name in trans.db_builds:
736            yield build_name, dbkey, ( dbkey == last_used_build )
737    def get_legal_values( self, trans, other_values ):
738        return set( dbkey for dbkey, _ in trans.db_builds )
739
740class ColumnListParameter( SelectToolParameter ):
741    """
742    Select list that consists of either the total number of columns or only
743    those columns that contain numerical values in the associated DataToolParameter.
744   
745    # TODO: we need better testing here, but not sure how to associate a DatatoolParameter with a ColumnListParameter
746    # from a twill perspective...
747
748    >>> # Mock up a history (not connected to database)
749    >>> from galaxy.model import History, HistoryDatasetAssociation
750    >>> from galaxy.util.bunch import Bunch
751    >>> from galaxy.model.mapping import context as sa_session
752    >>> hist = History()
753    >>> sa_session.add( hist )
754    >>> sa_session.flush()
755    >>> hist.add_dataset( HistoryDatasetAssociation( id=1, extension='interval', create_dataset=True, sa_session=sa_session ) )
756    >>> dtp =  DataToolParameter( None, XML( '<param name="blah" type="data" format="interval"/>' ) )
757    >>> print dtp.name
758    blah
759    >>> clp = ColumnListParameter ( None, XML( '<param name="numerical_column" type="data_column" data_ref="blah" numerical="true"/>' ) )
760    >>> print clp.name
761    numerical_column
762    """
763    def __init__( self, tool, elem ):
764        SelectToolParameter.__init__( self, tool, elem )
765        self.tool = tool
766        self.numerical = string_as_bool( elem.get( "numerical", False ))
767        self.force_select = string_as_bool( elem.get( "force_select", True ))
768        self.accept_default = string_as_bool( elem.get( "accept_default", False ))
769        self.data_ref = elem.get( "data_ref", None )
770        self.default_value = elem.get( "default_value", None )
771        self.is_dynamic = True
772    def from_html( self, value, trans=None, context={} ):
773        """
774        Label convention prepends column number with a 'c', but tool uses the integer. This
775        removes the 'c' when entered into a workflow.
776        """
777        def _strip_c( column ):
778            column = column.strip().lower()
779            if column.startswith( 'c' ):
780                column = column[1:]
781            return column
782        if self.multiple:
783            #split on newline and ,
784            if value:
785                column_list = []
786                if not isinstance( value, list ):
787                    value = value.split( '\n' )
788                for column in value:
789                    for column2 in column.split( ',' ):
790                        column2 = column2.strip()
791                        if column2:
792                            column_list.append( column2 )
793                value = map( _strip_c, column_list )
794            else:
795                value = []
796        else:
797            if value:
798                value = _strip_c( value )
799            else:
800                value = None
801        return super( ColumnListParameter, self ).from_html( value, trans, context )
802    def get_column_list( self, trans, other_values ):
803        """
804        Generate a select list containing the columns of the associated
805        dataset (if found).
806        """
807        column_list = []
808        # No value indicates a configuration error, the named DataToolParameter
809        # must preceed this parameter in the config
810        assert self.data_ref in other_values, "Value for associated DataToolParameter not found"
811        # Get the value of the associated DataToolParameter (a dataset)
812        dataset = other_values[ self.data_ref ]
813        # Check if a dataset is selected
814        if dataset is None or dataset == '':
815            # NOTE: Both of these values indicate that no dataset is selected.
816            #       However, 'None' indicates that the dataset is optional
817            #       while '' indicates that it is not. Currently column
818            #       parameters do not work well with optional datasets
819            return column_list
820        # Generate options
821        if not dataset.metadata.columns:
822            if self.accept_default:
823                column_list.append( self.default_value or '1' )
824            return column_list
825        if not self.force_select:
826            column_list.append( 'None' )
827        if self.numerical:
828            # If numerical was requsted, filter columns based on metadata
829            for i, col in enumerate( dataset.metadata.column_types ):
830                if col == 'int' or col == 'float':
831                    column_list.append( str( i + 1 ) )
832        else:
833            for i in range(0, dataset.metadata.columns):
834                column_list.append( str( i + 1 ) )
835        return column_list
836    def get_options( self, trans, other_values ):
837        options = []
838        column_list = self.get_column_list( trans, other_values )
839        if len( column_list ) > 0 and not self.force_select:
840            options.append( ('?', 'None', False) )
841        for col in column_list:
842            if col != 'None':
843                options.append( ( 'c' + col, col, False ) )
844        return options
845    def get_initial_value( self, trans, context ):
846        if self.default_value is not None:
847            # dataset not ready / in workflow / etc
848            if self.need_late_validation( trans, context ):
849                return UnvalidatedValue( self.default_value )
850            return self.default_value
851        return SelectToolParameter.get_initial_value( self, trans, context )
852    def get_legal_values( self, trans, other_values ):
853        return set( self.get_column_list( trans, other_values ) )
854    def get_dependencies( self ):
855        return [ self.data_ref ]
856    def need_late_validation( self, trans, context ):
857        if super( ColumnListParameter, self ).need_late_validation( trans, context ):
858            return True
859        # Get the selected dataset if selected
860        dataset = context[ self.data_ref ]
861        if dataset:
862            # Check if the dataset does not have the expected metadata for columns
863            if not dataset.metadata.columns:
864                # Only allow late validation if the dataset is not yet ready
865                # (since we have reason to expect the metadata to be ready eventually)
866                if dataset.is_pending:
867                    return True
868        # No late validation
869        return False
870       
871
872class DrillDownSelectToolParameter( SelectToolParameter ):
873    """
874    Parameter that takes on one (or many) of a specific set of values.
875    Creating a hierarchical select menu, which allows users to 'drill down' a tree-like set of options.
876   
877    >>> p = DrillDownSelectToolParameter( None, XML(
878    ... '''
879    ... <param name="some_name" type="drill_down" display="checkbox" hierarchy="recurse" multiple="true">
880    ...   <options>
881    ...    <option name="Heading 1" value="heading1">
882    ...        <option name="Option 1" value="option1"/>
883    ...        <option name="Option 2" value="option2"/>
884    ...        <option name="Heading 1" value="heading1">
885    ...          <option name="Option 3" value="option3"/>
886    ...          <option name="Option 4" value="option4"/>
887    ...        </option>
888    ...    </option>
889    ...    <option name="Option 5" value="option5"/>
890    ...   </options>
891    ... </param>
892    ... ''' ) )
893    >>> print p.get_html()
894    <div><ul class="toolParameterExpandableCollapsable">
895    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="some_name" value="heading1"">Heading 1
896    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
897    <li><input type="checkbox" name="some_name" value="option1"">Option 1
898    </li>
899    <li><input type="checkbox" name="some_name" value="option2"">Option 2
900    </li>
901    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="some_name" value="heading1"">Heading 1
902    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
903    <li><input type="checkbox" name="some_name" value="option3"">Option 3
904    </li>
905    <li><input type="checkbox" name="some_name" value="option4"">Option 4
906    </li>
907    </ul>
908    </li>
909    </ul>
910    </li>
911    <li><input type="checkbox" name="some_name" value="option5"">Option 5
912    </li>
913    </ul></div>
914    >>> p = DrillDownSelectToolParameter( None, XML(
915    ... '''
916    ... <param name="some_name" type="drill_down" display="radio" hierarchy="recurse" multiple="false">
917    ...   <options>
918    ...    <option name="Heading 1" value="heading1">
919    ...        <option name="Option 1" value="option1"/>
920    ...        <option name="Option 2" value="option2"/>
921    ...        <option name="Heading 1" value="heading1">
922    ...          <option name="Option 3" value="option3"/>
923    ...          <option name="Option 4" value="option4"/>
924    ...        </option>
925    ...    </option>
926    ...    <option name="Option 5" value="option5"/>
927    ...   </options>
928    ... </param>
929    ... ''' ) )
930    >>> print p.get_html()
931    <div><ul class="toolParameterExpandableCollapsable">
932    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="some_name" value="heading1"">Heading 1
933    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
934    <li><input type="radio" name="some_name" value="option1"">Option 1
935    </li>
936    <li><input type="radio" name="some_name" value="option2"">Option 2
937    </li>
938    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="some_name" value="heading1"">Heading 1
939    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
940    <li><input type="radio" name="some_name" value="option3"">Option 3
941    </li>
942    <li><input type="radio" name="some_name" value="option4"">Option 4
943    </li>
944    </ul>
945    </li>
946    </ul>
947    </li>
948    <li><input type="radio" name="some_name" value="option5"">Option 5
949    </li>
950    </ul></div>
951    >>> print p.options
952    [{'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': []}]
953    """
954    def __init__( self, tool, elem, context=None ):
955        def recurse_option_elems( cur_options, option_elems ):
956            for option_elem in option_elems:
957                selected = string_as_bool( option_elem.get( 'selected', False ) )
958                cur_options.append( { 'name':option_elem.get( 'name' ), 'value': option_elem.get( 'value'), 'options':[], 'selected':selected  } )
959                recurse_option_elems( cur_options[-1]['options'], option_elem.findall( 'option' ) )
960        ToolParameter.__init__( self, tool, elem )
961        self.multiple = string_as_bool( elem.get( 'multiple', False ) )
962        self.display = elem.get( 'display', None )
963        self.hierarchy = elem.get( 'hierarchy', 'exact' ) #exact or recurse
964        self.separator = elem.get( 'separator', ',' )
965        from_file = elem.get( 'from_file', None )
966        if from_file:
967            if not os.path.isabs( from_file ):
968                from_file = os.path.join( tool.app.config.tool_data_path, from_file )
969            elem = XML( "<root>%s</root>" % open( from_file ).read() )
970        self.is_dynamic = False
971        self.dynamic_options = None #backwards compatibility with SelectToolParameter's old dynamic options and late validation
972        self.options = []
973        self.filtered = {}
974        if elem.find( 'filter' ):
975            self.is_dynamic = True
976            for filter in elem.findall( 'filter' ):
977                #currently only filtering by metadata key matching input file is allowed
978                if filter.get( 'type' ) == 'data_meta':
979                    if filter.get( 'data_ref' ) not in self.filtered:
980                        self.filtered[filter.get( 'data_ref' )] = {}
981                    if filter.get( 'meta_key' ) not in self.filtered[filter.get( 'data_ref' )]:
982                        self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )] = {}
983                    if filter.get( 'value' ) not in self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )]:
984                        self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )][filter.get( 'value' )] = []
985                    recurse_option_elems( self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )][filter.get( 'value' )], filter.find( 'options' ).findall( 'option' ) )
986        else:
987            recurse_option_elems( self.options, elem.find( 'options' ).findall( 'option' ) )
988   
989    def get_options( self, trans=None, value=None, other_values={} ):
990        if self.is_dynamic:
991            options = []
992            for filter_key, filter_value in self.filtered.iteritems():
993                dataset = other_values[filter_key]
994                if dataset.__class__.__name__.endswith( "DatasetFilenameWrapper" ): #this is a bad way to check for this, but problems importing class ( due to circular imports? )
995                    dataset = dataset.dataset
996                if dataset:
997                    for meta_key, meta_dict in filter_value.iteritems():
998                        check_meta_val = dataset.metadata.spec[meta_key].param.to_string( dataset.metadata.get( meta_key ) )
999                        if check_meta_val in meta_dict:
1000                            options.extend( meta_dict[check_meta_val] )
1001            return options
1002        return self.options
1003   
1004    def get_legal_values( self, trans, other_values ):
1005        def recurse_options( legal_values, options ):
1006            for option in options:
1007                legal_values.append( option['value'] )
1008                recurse_options( legal_values, option['options'] )
1009        legal_values = []
1010        recurse_options( legal_values, self.get_options( trans=trans, other_values=other_values ) )
1011        return legal_values
1012   
1013    def get_html( self, trans=None, value=None, other_values={} ):
1014        """
1015        Returns the html widget corresponding to the paramter.
1016        Optionally attempt to retain the current value specific by 'value'
1017        """       
1018        return self.get_html_field( trans, value, other_values ).get_html()
1019               
1020    def get_html_field( self, trans=None, value=None, other_values={} ):
1021        # Dynamic options are not yet supported in workflow, allow
1022        # specifying the value as text for now.
1023        if self.need_late_validation( trans, other_values ):
1024            if value is not None:
1025                assert isinstance( value, UnvalidatedValue )
1026                value = value.value
1027            if self.multiple:
1028                if value is None:
1029                    value = ""
1030                else:
1031                    value = "\n".join( value )
1032                return form_builder.TextArea( self.name, value=value )
1033            else:
1034                return form_builder.TextField( self.name, value=(value or "") )
1035        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 )
1036   
1037    def from_html( self, value, trans=None, other_values={} ):
1038        if self.need_late_validation( trans, other_values ):
1039            if self.multiple:
1040                if value == '': #No option selected
1041                    value = None
1042                else:
1043                    value = value.split( "\n" )
1044            return UnvalidatedValue( value )
1045        if not value: return None
1046        if not isinstance( value, list ):
1047            value = [value]
1048        if not( self.repeat ) and len( value ) > 1:
1049            assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
1050        rval = []
1051        for val in value:
1052            if val not in self.get_legal_values( trans, other_values ): raise ValueError( "An invalid option was selected, please verify" )
1053            rval.append( util.restore_text( val ) )
1054        return rval
1055   
1056    def to_param_dict_string( self, value, other_values={} ):
1057        def get_options_list( value ):
1058            def get_base_option( value, options ):
1059                for option in options:
1060                    if value == option['value']:
1061                        return option
1062                    rval = get_base_option( value, option['options'] )
1063                    if rval: return rval
1064                return None #not found
1065            def recurse_option( option_list, option ):
1066                if not option['options']:
1067                    option_list.append( option['value'] )
1068                else:
1069                    for opt in option['options']:
1070                        recurse_option( option_list, opt )
1071            rval = []
1072            recurse_option( rval, get_base_option( value, self.get_options( other_values = other_values ) ) )
1073            return rval or [value]
1074       
1075        if value is None: return "None"
1076        rval = []
1077        if self.hierarchy == "exact":
1078            rval = value
1079        else:
1080            for val in value:
1081                options = get_options_list( val )
1082                rval.extend( options )
1083        if len( rval ) > 1:
1084            if not( self.repeat ):
1085                assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
1086        rval = self.separator.join( rval )
1087        if self.tool is None or self.tool.options.sanitize:
1088            if self.sanitizer:
1089                rval = self.sanitizer.sanitize_param( rval )
1090            else:
1091                rval = sanitize_param( rval )
1092        return rval
1093   
1094    def get_initial_value( self, trans, context ):
1095        def recurse_options( initial_values, options ):
1096            for option in options:
1097                if option['selected']:
1098                    initial_values.append( option['value'] )
1099                recurse_options( initial_values, option['options'] )
1100        # More working around dynamic options for workflow
1101        if self.need_late_validation( trans, context ):
1102            # Really the best we can do?
1103            return UnvalidatedValue( None )
1104        initial_values = []
1105        recurse_options( initial_values, self.get_options( trans=trans, other_values=context ) )
1106        return initial_values
1107
1108    def value_to_display_text( self, value, app ):
1109        def get_option_display( value, options ):
1110            for option in options:
1111                if value == option['value']:
1112                    return option['name']
1113                rval = get_option_display( value, option['options'] )
1114                if rval: return rval
1115            return None #not found
1116       
1117        if isinstance( value, UnvalidatedValue ):
1118            suffix = "\n(value not yet validated)"
1119            value = value.value
1120        else:
1121            suffix = ""
1122        if not value:
1123            value = []
1124        elif not isinstance( value, list ):
1125            value = [ value ]
1126        # FIXME: Currently only translating values back to labels if they
1127        #        are not dynamic
1128        if self.is_dynamic:
1129            if value:
1130                if isinstance( value, list ):
1131                    rval = value
1132                else:
1133                    rval = [ value ]
1134            else:
1135                rval = []
1136        else:
1137            rval = []
1138            for val in value:
1139                rval.append( get_option_display( val, self.options ) or val )
1140        return "\n".join( rval ) + suffix
1141       
1142    def get_dependencies( self ):
1143        """
1144        Get the *names* of the other params this param depends on.
1145        """
1146        return self.filtered.keys()
1147
1148class DummyDataset( object ):
1149    pass
1150
1151class DataToolParameter( ToolParameter ):
1152    # TODO, Nate: Make sure the following unit tests appropriately test the dataset security
1153    # components.  Add as many additional tests as necessary.
1154    """
1155    Parameter that takes on one (or many) or a specific set of values.
1156
1157    TODO: There should be an alternate display that allows single selects to be
1158          displayed as radio buttons and multiple selects as a set of checkboxes
1159
1160    TODO: The following must be fixed to test correctly for the new security_check tag in the DataToolParameter ( the last test below is broken )
1161    Nate's next passs at the dataset security stuff will dramatically alter this anyway.
1162    """
1163
1164    def __init__( self, tool, elem ):
1165        ToolParameter.__init__( self, tool, elem )
1166        # Add metadata validator
1167        if not string_as_bool( elem.get( 'no_validation', False ) ):
1168            self.validators.append( validation.MetadataValidator() )
1169        # Build tuple of classes for supported data formats
1170        formats = []
1171        self.extensions = elem.get( 'format', 'data' ).split( "," )
1172        for extension in self.extensions:
1173            extension = extension.strip()
1174            if tool is None:
1175                #This occurs for things such as unit tests
1176                import galaxy.datatypes.registry
1177                formats.append( galaxy.datatypes.registry.Registry().get_datatype_by_extension( extension.lower() ).__class__ )
1178            else:
1179                formats.append( tool.app.datatypes_registry.get_datatype_by_extension( extension.lower() ).__class__ )
1180        self.formats = tuple( formats )
1181        self.multiple = string_as_bool( elem.get( 'multiple', False ) )
1182        # Optional DataToolParameters are used in tools like GMAJ and LAJ
1183        self.optional = string_as_bool( elem.get( 'optional', False ) )
1184        # TODO: Enhance dynamic options for DataToolParameters. Currently,
1185        #       only the special case key='build' of type='data_meta' is
1186        #       a valid filter
1187        options = elem.find( 'options' )
1188        if options is None:
1189            self.options = None
1190        else:
1191            self.options = dynamic_options.DynamicOptions( options, self )
1192        self.is_dynamic = self.options is not None
1193        # Load conversions required for the dataset input
1194        self.conversions = []
1195        for conv_elem in elem.findall( "conversion" ):
1196            name = conv_elem.get( "name" ) #name for commandline substitution
1197            conv_extensions = conv_elem.get( "type" ) #target datatype extension
1198            # FIXME: conv_extensions should be able to be an ordered list
1199            assert None not in [ name, type ], 'A name (%s) and type (%s) are required for explicit conversion' % ( name, type )
1200            conv_types = tool.app.datatypes_registry.get_datatype_by_extension( conv_extensions.lower() ).__class__
1201            self.conversions.append( ( name, conv_extensions, conv_types ) )
1202
1203    def get_html_field( self, trans=None, value=None, other_values={} ):
1204        filter_value = None
1205        if self.options:
1206            try:
1207                filter_value = self.options.get_options( trans, other_values )[0][0]
1208            except IndexError:
1209                pass #no valid options
1210        assert trans is not None, "DataToolParameter requires a trans"
1211        history = trans.get_history()
1212        assert history is not None, "DataToolParameter requires a history"
1213        if value is not None:
1214            if type( value ) != list:
1215                value = [ value ]
1216        field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values = self.refresh_on_change_values )
1217        # CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()
1218        def dataset_collector( hdas, parent_hid ):
1219            current_user_roles = trans.get_current_user_roles()
1220            for i, hda in enumerate( hdas ):
1221                if len( hda.name ) > 30:
1222                    hda_name = '%s..%s' % ( hda.name[:17], hda.name[-11:] )
1223                else:
1224                    hda_name = hda.name
1225                if parent_hid is not None:
1226                    hid = "%s.%d" % ( parent_hid, i + 1 )
1227                else:
1228                    hid = str( hda.hid )
1229                if not hda.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED] and \
1230                    hda.visible and \
1231                    trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
1232                    # If we are sending data to an external application, then we need to make sure there are no roles
1233                    # associated with the dataset that restrict it's access from "public".
1234                    if self.tool and self.tool.tool_type == 'data_destination' and not trans.app.security_agent.dataset_is_public( hda.dataset ):
1235                        continue
1236                    if self.options and hda.get_dbkey() != filter_value:
1237                        continue
1238                    if isinstance( hda.datatype, self.formats):
1239                        selected = ( value and ( hda in value ) )
1240                        field.add_option( "%s: %s" % ( hid, hda_name ), hda.id, selected )
1241                    else:
1242                        target_ext, converted_dataset = hda.find_conversion_destination( self.formats, converter_safe = self.converter_safe( other_values, trans ) )
1243                        if target_ext:
1244                            if converted_dataset:
1245                                hda = converted_dataset
1246                            if not trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
1247                                continue
1248                            selected = ( value and ( hda in value ) )
1249                            field.add_option( "%s: (as %s) %s" % ( hid, target_ext, hda_name ), hda.id, selected )
1250                # Also collect children via association object
1251                dataset_collector( hda.children, hid )
1252        dataset_collector( history.active_datasets, None )
1253        some_data = bool( field.options )
1254        if some_data:
1255            if value is None or len( field.options ) == 1:
1256                # Ensure that the last item is always selected
1257                a, b, c = field.options[-1]
1258                if self.optional:
1259                    field.options[-1] = a, b, False
1260                else:
1261                    field.options[-1] = a, b, True
1262        if self.optional:
1263            if not value:
1264                field.add_option( "Selection is Optional", 'None', True )
1265            else:
1266                field.add_option( "Selection is Optional", 'None', False )
1267        return field
1268
1269    def get_initial_value( self, trans, context ):
1270        """
1271        NOTE: This is wasteful since dynamic options and dataset collection
1272              happens twice (here and when generating HTML).
1273        """
1274        # Can't look at history in workflow mode
1275        if trans.workflow_building_mode:
1276            return DummyDataset()
1277        assert trans is not None, "DataToolParameter requires a trans"
1278        history = trans.get_history()
1279        assert history is not None, "DataToolParameter requires a history"
1280        if self.optional:
1281            return None
1282        most_recent_dataset = [None]
1283        filter_value = None
1284        if self.options:
1285            try:
1286                filter_value = self.options.get_options( trans, context )[0][0]
1287            except IndexError:
1288                pass #no valid options
1289        def dataset_collector( datasets ):
1290            def is_convertable( dataset ):
1291                target_ext, converted_dataset = dataset.find_conversion_destination( self.formats, converter_safe = self.converter_safe( None, trans ) )
1292                if target_ext is not None:
1293                    return True
1294                return False
1295            for i, data in enumerate( datasets ):
1296                if data.visible and not data.deleted and data.state not in [data.states.ERROR, data.states.DISCARDED] and ( isinstance( data.datatype, self.formats) or is_convertable( data ) ):
1297                    if self.options and data.get_dbkey() != filter_value:
1298                        continue
1299                    most_recent_dataset[0] = data
1300                # Also collect children via association object
1301                dataset_collector( data.children )
1302        dataset_collector( history.datasets )
1303        most_recent_dataset = most_recent_dataset.pop()
1304        if most_recent_dataset is not None:
1305            return most_recent_dataset
1306        else:
1307            return ''
1308
1309    def from_html( self, value, trans, other_values={} ):
1310        # Can't look at history in workflow mode, skip validation and such,
1311        # although, this should never be called in workflow mode right?
1312        if trans.workflow_building_mode:
1313            return None
1314        if not value:
1315            raise ValueError( "History does not include a dataset of the required format / build" )
1316        if value in [None, "None"]:
1317            return None
1318        if isinstance( value, list ):
1319            return [ trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( v ) for v in value ]
1320        elif isinstance( value, trans.app.model.HistoryDatasetAssociation ):
1321            return value
1322        else:
1323            return trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( value )
1324
1325    def to_string( self, value, app ):
1326        if value is None or isinstance( value, str ):
1327            return value
1328        elif isinstance( value, DummyDataset ):
1329            return None
1330        return value.id
1331
1332    def to_python( self, value, app ):
1333        # Both of these values indicate that no dataset is selected.  However, 'None'
1334        # indicates that the dataset is optional, while '' indicates that it is not.
1335        if value is None or value == '' or value == 'None':
1336            return value
1337        return app.model.context.query( app.model.HistoryDatasetAssociation ).get( int( value ) )
1338
1339    def to_param_dict_string( self, value, other_values={} ):
1340        if value is None: return "None"
1341        return value.file_name
1342       
1343    def value_to_display_text( self, value, app ):
1344        if value:
1345            return "%s: %s" % ( value.hid, value.name )
1346        else:
1347            return "No dataset"
1348
1349    def get_dependencies( self ):
1350        """
1351        Get the *names* of the other params this param depends on.
1352        """
1353        if self.options:
1354            return self.options.get_dependency_names()
1355        else:
1356            return []
1357
1358    def converter_safe( self, other_values, trans ):
1359        if self.tool is None or self.tool.has_multiple_pages or not hasattr( trans, 'workflow_building_mode' ) or trans.workflow_building_mode:
1360            return False
1361        if other_values is None:
1362            return True # we don't know other values, so we can't check, assume ok
1363        converter_safe = [True]
1364        def visitor( prefix, input, value, parent = None ):
1365            if isinstance( input, SelectToolParameter ) and self.name in input.get_dependencies():
1366                if input.is_dynamic and ( input.dynamic_options or ( not input.dynamic_options and not input.options ) or not input.options.converter_safe ):
1367                    converter_safe[0] = False #This option does not allow for conversion, i.e. uses contents of dataset file to generate options
1368        self.tool.visit_inputs( other_values, visitor )
1369        return False not in converter_safe
1370
1371# class RawToolParameter( ToolParameter ):
1372#     """
1373#     Completely nondescript parameter, HTML representation is provided as text
1374#     contents.
1375#     
1376#     >>> p = RawToolParameter( None, XML(
1377#     ... '''
1378#     ... <param name="blah" type="raw">
1379#     ... <![CDATA[<span id="$name">Some random stuff</span>]]>
1380#     ... </param>
1381#     ... ''' ) )
1382#     >>> print p.name
1383#     blah
1384#     >>> print p.get_html().strip()
1385#     <span id="blah">Some random stuff</span>
1386#     """
1387#     def __init__( self, tool, elem ):
1388#         ToolParameter.__init__( self, tool, elem )
1389#         self.template = string.Template( elem.text )
1390#     def get_html( self, prefix="" ):
1391#         context = dict( self.__dict__ )
1392#         context.update( dict( prefix=prefix ) )
1393#         return self.template.substitute( context )
1394       
1395# class HistoryIDParameter( ToolParameter ):
1396#     """
1397#     Parameter that takes a name value, makes history.id available.
1398#     
1399#     FIXME: This is a hack (esp. if hidden params are a hack) but in order to
1400#            have the history accessable at the job level, it is necessary
1401#            I also probably wrote this docstring test thing wrong.
1402#     
1403#     >>> from galaxy.model import History
1404#     >>> from galaxy.util.bunch import Bunch
1405#     >>> hist = History( id=1 )
1406#     >>> p = HistoryIDParameter( None, XML( '<param name="blah" type="history"/>' ) )
1407#     >>> print p.name
1408#     blah
1409#     >>> html_string = '<input type="hidden" name="blah" value="%d">' % hist.id
1410#     >>> assert p.get_html( trans=Bunch( history=hist ) ) == html_string
1411#     """ 
1412#     def __init__( self, tool, elem ):
1413#         ToolParameter.__init__( self, tool, elem )
1414#     def get_html( self, trans, value=None, other_values={} ):
1415#         assert trans.history is not None, "HistoryIDParameter requires a history"
1416#         self.html = form_builder.HiddenField( self.name, trans.history.id ).get_html()
1417#         return self.html
1418
1419parameter_types = dict( text        = TextToolParameter,
1420                        integer     = IntegerToolParameter,
1421                        float       = FloatToolParameter,
1422                        boolean     = BooleanToolParameter,
1423                        genomebuild = GenomeBuildParameter,
1424                        select      = SelectToolParameter,
1425                        data_column = ColumnListParameter,
1426                        hidden      = HiddenToolParameter,
1427                        baseurl     = BaseURLToolParameter,
1428                        file        = FileToolParameter,
1429                        data        = DataToolParameter,
1430                        drill_down = DrillDownSelectToolParameter )
1431
1432class UnvalidatedValue( object ):
1433    """
1434    Wrapper to mark a value that has not been validated
1435    """
1436    def __init__( self, value ):
1437        self.value = value
1438    def __str__( self ):
1439        return str( self.value )
1440       
1441class RuntimeValue( object ):
1442    """
1443    Wrapper to note a value that is not yet set, but will be required at
1444    runtime.
1445    """
1446    pass
1447
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。