2424#
2525# See also the Boutiques repository: https://github.com/boutiques/boutiques
2626#
27- # The modules provides one main Ruby class, BoutiquesSupport::BoutiquesDescriptor,
27+ # The module provides one main Ruby class, BoutiquesSupport::BoutiquesDescriptor,
2828# and several smaller data classes representing components of a descriptor.
2929# These classes are subclasses of RestrictedHash, a type of Hash class that
3030# only recognize a select set of keys and raise an exception when other keys
5858# appear in RDOC-generated documentation. Among these, many are
5959# used by the BoutiquesTask integrator.
6060#
61- # # Returns the name of the tool NNNN, appropriate to use as
62- # # a class name as BoutiquesTask::NNNN
63- # desc.name_as_ruby_class
64- #
65- # # Returns all tags as a flat array
66- # desc.flat_tag_list
67- #
68- # # Finds a specific BoutiquesSupport:Input by ID
69- # desc.input_by_id(inputid)
70- #
71- # # Subset of the list of inputs with just the optional ones
72- # desc.optional_inputs
73- #
74- # # Subset of the list of inputs with just the mandatory ones
75- # desc.required_inputs
76- #
77- # # Subset of the list of inputs with just the multi-valued ones
78- # desc.list_inputs
79- #
80- # # Subset of the list of inputs with just the File inputs
81- # desc.file_inputs
82- #
83- # # List of File inputs that are optional
84- # desc.optional_file_inputs
85- #
86- # # List of File inputs that are mandatory
87- # desc.required_file_inputs
88- #
89- # # Returns the entry for a custom Boutiques integration module
90- # desc.custom_module_info(modulename)
91- #
92- # # Utility for building a replacement hash for the inputs based on
93- # # the values in invoke_structure
94- # desc.build_substitutions_by_tokens_hash(invoke_structure)
95- #
96- # # Utility to perform the subsitutions of tokens in a string
97- # desc.apply_substitutions(string, substitutions_by_tokens, to_strip=[])
98- #
99- # # Returns a new descriptor with the attributes in a canonical beautiful order
100- # desc.pretty_ordered
101- #
102- # # Generates a JSON with nice spacing
103- # desc.super_pretty_json
10461#
10562module BoutiquesSupport
10663
@@ -165,6 +122,7 @@ def initialize(hash={})
165122 self
166123 end
167124
125+ # Creates a new Boutiques object from string
168126 def self . new_from_string ( text )
169127 json = JSON . parse ( text )
170128 errors = BoutiquesSupport . validate ( json )
@@ -173,13 +131,14 @@ def self.new_from_string(text)
173131 cb_error "Invalid Boutiques descriptor\n " + ( errors . map { |e | e [ :message ] } . join ( "\n " ) )
174132 end
175133
134+ # Creates a new Boutiques object from a documents stored in a given path
176135 def self . new_from_file ( path )
177136 obj = self . new_from_string ( File . read ( path ) )
178137 obj . from_file = path
179138 obj
180139 end
181140
182- def validate
141+ def validate #:nodoc:
183142 BoutiquesSupport . validate ( self ) # amazingly, the JSON validator also work with our descriptor class
184143 end
185144
@@ -226,6 +185,7 @@ def name_as_ruby_class
226185 . camelize
227186 end
228187
188+ # Returns all tags as a flat arra
229189 def flat_tag_list
230190 tags = self . tags
231191 return [ ] if ! tags
@@ -235,31 +195,38 @@ def flat_tag_list
235195 end . flatten
236196 end
237197
198+ # Finds a specific Input by id
238199 def input_by_id ( inputid )
239200 inputs . detect { |x | x . id == inputid } or
240201 cb_error "No input found with ID '#{ inputid } '"
241202 end
242203
204+ # Lists optional inputs
243205 def optional_inputs
244206 inputs . select { |x | x . optional }
245207 end
246208
209+ # Lists required inputs
247210 def required_inputs
248211 inputs . select { |x | ! x . optional }
249212 end
250213
214+ # Lists inputs
251215 def list_inputs
252216 inputs . select { |x | x . list }
253217 end
254218
219+ # Lists File inputs
255220 def file_inputs
256221 inputs . select { |x | x . type == 'File' }
257222 end
258223
224+ # Lists optional File inputs
259225 def optional_file_inputs
260226 file_inputs . select { |x | x . optional }
261227 end
262228
229+ # Lists mandatory File inputs
263230 def required_file_inputs
264231 file_inputs . select { |x | ! x . optional }
265232 end
@@ -338,7 +305,7 @@ def build_substitutions_by_tokens_hash(invoke_structure)
338305 end . compact . to_h
339306 end
340307
341- # Replaces in +string+ all occurences of the keys in
308+ # Replaces in +string+ all occurrences of the keys in
342309 # +substitutions_by_tokens+ by the associated values.
343310 # This is typically used to build a templated string
344311 # using the "value-key" of the inputs of the descriptor.
@@ -519,6 +486,136 @@ def super_pretty_json
519486 new_json
520487 end
521488
489+ #-------------------------------------------------------------------------
490+ # Methods to access and document CBRAIN specific custom properties
491+ #-------------------------------------------------------------------------
492+ # see public/doc/boutiques_extensions for a list of these custom properties
493+
494+ # Returns a string with name(s) and emails(s) of the Boutiques descriptor authors, enlisted in
495+ # "cbrain:author" custom property of the descriptors. Emails are optional
496+ # and should be in angle brackets
497+ #
498+ # For example, given the descriptor with
499+ #
500+ # "custom": { "cbrain:author": "Full Name <email@address.ca>, Co-author Name <anotheremail@address.org>" }
501+ #
502+ # The method returns string
503+ # "Full Name <email@address.ca>, Co-author Name <anotheremail@address.org>"
504+ def custom_author
505+ authors = self . custom [ 'cbrain:author' ]
506+ return authors if authors is_a? String
507+ return authors . join ( ", " ) # if author field is arrays
508+ end
509+
510+ # Returns Boutiques CBRAIN custom property indicating
511+ # are forking sub-task(s) allowed. To submit a subtask, a task must create a JSON file
512+ # named ".new-task-*.json" in the root of its
513+ # work directory. An example of property definition in a tool descriptor:
514+ #
515+ # "custom: {
516+ # "cbrain:can-submit-new-tasks": true
517+ # }
518+ def custom_can_submit_new_tasks
519+ return self . custom [ "cbrain:can-submit-new-tasks" ]
520+ end
521+
522+ # Returns Boutiques CBRAIN custom property indicating
523+ # the outputs which will not be saved.
524+ # An example of property definition in a tool descriptor:
525+ #
526+ # "custom: {
527+ # "cbrain:ignore_outputs": [output_id_1, output_id_2, output_id_3 ... ]
528+ # }
529+ def custom_ignore_outputs
530+ return self . custom [ "cbrain:ignore_outputs" ]
531+ end
532+
533+ # Returns Boutiques CBRAIN custom property indicating
534+ # inputs which are saved back to the dataprovider
535+ # (the original data will be mutated).
536+ #
537+ # An example of property definition in a tool descriptor:
538+ # "custom: {
539+ # "cbrain:save_back_inputs": [id_1, id_2, id_3 ...]
540+ # }
541+ def custom_save_back_inputs
542+ return self . custom [ "cbrain:save_back_inputs" ]
543+ end
544+
545+ # Returns Boutiques CBRAIN custom property indicating
546+ # that the tool does not modify inputs.
547+ # An example of property definition in a tool descriptor:
548+ #
549+ # "custom: {
550+ # "cbrain:readonly-input-files": true
551+ # }
552+ def custom_readonly_input_files
553+ return self . custom [ "cbrain:readonly-input-files" ]
554+ end
555+
556+ # Returns Boutiques CBRAIN custom property indicating
557+ # if this task may alter its input files.
558+ # An example of property definition in a tool descriptor:
559+ #
560+ # "custom: {
561+ # "cbrain:alters-input-files": true
562+ # }
563+ def custom_alters_input_files
564+ return self . custom [ "cbrain:alters-input-files" ]
565+ end
566+
567+ # Returns Boutiques CBRAIN custom property indicating for which outputs
568+ # the usual practice of adding a run id to output file names is cancelled,
569+ # list of output IDs where no run id inserted. Only allowed for MultiLevel
570+ # data-providers with "browse path" capability.
571+ # For listed outputs ids new results overwrite old files.
572+ # An example of property definition in a tool descriptor:
573+ #
574+ # "custom: {
575+ # "cbrain:no-run-id-for-outputs": "id_1, id_2, id_3 .."
576+ # }
577+ def custom_no_run_id_for_outputs
578+ return self . custom [ "cbrain:no-run-id-for-outputs" ]
579+ end
580+
581+ # Returns Boutiques CBRAIN custom property indicating
582+ # for which inputs an empty string is a valid input.
583+ # An example of property definition in a tool descriptor:
584+ #
585+ # "custom: {
586+ # "cbrain:allow_empty_strings": [input_id]
587+ # }
588+ def custom_allow_empty_strings
589+ return self . custom [ "cbrain:allow_empty_strings" ]
590+ end
591+
592+ # Experimental feature that affects the way tasks are executed.
593+ # The default implied value is 'simulate'
594+ # In the mode 'simulate', at the moment of creating
595+ # the tool's script in cluster_commands(), the
596+ # output of 'bosh exec simulate' will be substituted in
597+ # the script to generate the tool's command.
598+ # In the mode 'launch', an actual 'bosh exec launch' command
599+ # will be put in the script instead.
600+ # An example of property definition in a tool descriptor:
601+ #
602+ # "custom: {
603+ # "cbrain:boutiques_bosh_exec_mode": "launch"
604+ # }
605+ def custom_boutiques_bosh_exec_mode
606+ return self . custom [ "cbrain:boutiques_bosh_exec_mode" ]
607+ end
608+
609+ # An advanced feature for seasoned CBRAIN experts only. That allows
610+ # overwrite the standard task behavior with custom class.
611+ # An example of property definition in a tool descriptor:
612+ # "custom: {
613+ # "cbrain:inherits-from-class": "MyClassName"
614+ # }
615+ def custom_inherits_from_class
616+ return self . custom [ "cbrain:inherits-from-class" ]
617+ end
618+
522619 end # class BoutiquesSupport::BoutiquesDescriptor
523620
524621 #------------------------------------------------------
@@ -543,23 +640,23 @@ def dup #:nodoc:
543640 copy
544641 end
545642
546- # This method return the parameter name for an input identified
643+ # This method returns the parameter name for an input identified
547644 # by input_id.
548645 # We put all input Boutiques parameters under a 'invoke' substructure.
549646 # E.g. for a input with ID 'abcd' in a task, we'll find the value
550647 # in task.params['invoke']['abcd'] and the parameter name is thus
551648 # "invoke[abcd]". The as_list option appends "[]" to the name
552649 # to make it an array parameter.
553- def self . cb_invoke_name ( input_id , as_list = nil )
650+ def self . cb_invoke_name ( input_id , as_list = nil ) #:nodoc:
554651 return "invoke[#{ input_id } ][]" if as_list
555652 return "invoke[#{ input_id } ]"
556653 end
557654
558- def self . cb_invoke_html_name ( input_id , force_list = nil )
655+ def self . cb_invoke_html_name ( input_id , force_list = nil ) #:nodoc:
559656 self . cb_invoke_name ( input_id , force_list ) . to_la
560657 end
561658
562- def self . cb_invoke_html_id ( input_id , force_list = nil )
659+ def self . cb_invoke_html_id ( input_id , force_list = nil ) #:nodoc:
563660 self . cb_invoke_name ( input_id , force_list ) . to_la_id
564661 end
565662
@@ -576,12 +673,12 @@ def cb_invoke_name(force_list = nil)
576673 self . class . cb_invoke_name ( self . id , as_list )
577674 end
578675
579- def cb_invoke_html_name ( force_list = nil )
676+ def cb_invoke_html_name ( force_list = nil ) #:nodoc:
580677 as_list = ( self . list && force_list . nil? ) || force_list == true
581678 self . class . cb_invoke_html_name ( self . id , as_list )
582679 end
583680
584- def cb_invoke_html_id ( force_list = nil )
681+ def cb_invoke_html_id ( force_list = nil ) #:nodoc:
585682 as_list = ( self . list && force_list . nil? ) || force_list == true
586683 self . class . cb_invoke_html_id ( self . id , as_list )
587684 end
0 commit comments