libis-workflow 2.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9790f9c81c7096dd8871ffd8d586a233543e68d9
4
+ data.tar.gz: 605daffb9699e2deb3da97b921be71182ef5d65a
5
+ SHA512:
6
+ metadata.gz: 4ea5a60fb96c162c8a9c138613bf057cfac85ecde4d4dc42a5ea28833b98480ed1113ecf121fb28c3c5fe63f47813e4e13185dee7c406c1fb08246a307cc5cb7
7
+ data.tar.gz: 2cfe51aaa9e05dde6d6b5cba4aee591e614121d04f1121710f369787baffae0c326903c6b308a57c2b46058a8ebd780546499cba4b5c5bcf93aecf457b537ea8
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: TMosCEIw4eu2hK05NxyY2UYIRJYQPzemt
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ Gemfile.lock
30
+ .ruby-version
31
+ .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
35
+
36
+ .idea/
data/.travis.yml ADDED
@@ -0,0 +1,33 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.1.0
6
+ - 2.2.0
7
+ - ruby-head
8
+ - jruby-19mode
9
+ jdk:
10
+ - openjdk7
11
+ - oraclejdk7
12
+ - oraclejdk8
13
+ matrix:
14
+ exclude:
15
+ - rvm: 1.9.3
16
+ jdk: oraclejdk7
17
+ - rvm: 1.9.3
18
+ jdk: oraclejdk8
19
+ - rvm: 2.1.0
20
+ jdk: oraclejdk7
21
+ - rvm: 2.1.0
22
+ jdk: oraclejdk8
23
+ - rvm: 2.2.0
24
+ jdk: oraclejdk7
25
+ - rvm: 2.2.0
26
+ jdk: oraclejdk8
27
+ - rvm: ruby-head
28
+ jdk: oraclejdk7
29
+ - rvm: ruby-head
30
+ jdk: oraclejdk8
31
+ branches:
32
+ only:
33
+ - master
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec name: 'libis-workflow', development_group: :test
4
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 LIBIS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,296 @@
1
+
2
+ [![Build Status](https://travis-ci.org/Kris-LIBIS/workflow.svg?branch=master)](https://travis-ci.org/Kris-LIBIS/workflow)
3
+ [![Coverage Status](https://img.shields.io/coveralls/Kris-LIBIS/workflow.svg)](https://coveralls.io/r/Kris-LIBIS/workflow)
4
+
5
+ # LIBIS Workflow
6
+
7
+ LIBIS Workflow framework
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'libis-workflow'
15
+ ```
16
+
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install 'libis-workflow'
25
+
26
+ ## Architecture
27
+
28
+ This gem is essentially a simple, custom workflow system. The core of the workflow are the tasks. You can - and should -
29
+ create your own tasks by creating new classes and include ::Libis::Workflow::Task. The ::Libis::Workflow::Task module
30
+ and the included ::Libis::Workflow::Base::Logger module provide the necessary attributes and methods to make them work
31
+ in the workflow. See the detailed documentation for the modules for more information.
32
+
33
+ The objects that the tasks will be working on should include the ::Libis::Workflow::WorkItem module.
34
+ When working with file objects the module ::Libis::Workflow::FileItem and/or ::Libis::Workflow::DirItem modules should
35
+ be included for additional file-specific functionality.
36
+ Work items can be organized in different types and a hierarchical structure.
37
+
38
+ All the tasks will be organized into a ::Libis::Workflow::WorkflowDefinition which will be able to execute the tasks in
39
+ proper order on all the WorkItems supplied/collected. Each task can be implemented with code to run or simply contain a
40
+ list of child tasks.
41
+
42
+ Two tasks are predefined:
43
+ ::Libis::Workflow::Tasks::VirusChecker - runs a virus check on each WorkItem that is also a FileItem.
44
+ ::Libis::Workflow::Tasks::Analyzer - analyzes the workflow run and summarizes the results. It is always included as the
45
+ last task by the workflow unless you supply a closing task called 'Analyzer' yourself.
46
+
47
+ The whole ingester workflow is configured by a Singleton object ::Libis::Workflow::Config which contains settings for
48
+ logging, paths where tasks and workitems can be found and the path to the virus scanner program.
49
+
50
+ ## Usage
51
+
52
+ You should start by including the following line in your source code:
53
+
54
+ ```ruby
55
+ require 'libis-workflow'
56
+ ```
57
+
58
+ This will load all of the Libis Workflow framework into your environment, but including only the required parts is OK as
59
+ well. This is shown in the examples below.
60
+
61
+ ### Workflows
62
+
63
+ A ::Libis::Workflow::WorkflowDefinition instance contains the definition of a workflow. Once instantiated, it can be run
64
+ by calling the 'run' method. This will create a ::Libis::Workflow::WorkflowRun instance, configure it and call the 'run'
65
+ method on it. The Workflow constructor takes no arguments, but is should be configured by calling the 'set_config'
66
+ method with the workflow configuration as an argument. The 'run' method takes an option Hash as argument.
67
+
68
+ #### Workflow configuration
69
+
70
+ A workflow configuration is a Hash with:
71
+ * tasks: Array of task descriptions
72
+ * start_object: String with class name of the starting object to be created. An istance of this class will be created
73
+ for each run and serves as the root work item for that particular run.
74
+ * input: Hash with input variable definitions
75
+
76
+ ##### Task description
77
+
78
+ is a Hash with:
79
+ * class: String with class name of the task
80
+ * name: String with the name of the task
81
+ * tasks: Array with task definitions of sub-tasks
82
+ * options: Hash with additional task configuration options (see 'Tasks - Configuration' for more info)
83
+
84
+ If 'class' is not present, the default '::Libis::Workflow::Task' with the given name will be instantiated, which simply
85
+ iterates over the child items of the given work item and performs each sub-task on each of the child items. If a 'class'
86
+ value is given, an instance of that class will be created and the task will be handed the work item to process on. See
87
+ the chapter on 'Tasks' below for more information on tasks.
88
+
89
+ ##### Input variable definition
90
+
91
+ The key of the input Hash is the unique id of the variable. The value is a Hash with:
92
+ * name: String with the name of the input variable
93
+ This value is used for display only
94
+ * description: String with descriptive text explaining the use/meaning of the variable
95
+ * type: String with the type of the variable
96
+ Currently only 'String', 'Time' and 'Boolean' are supported. If the value is not present, 'String' is asumed.
97
+ * default: String with the default value
98
+ If the default value contains the string %s, it will be replaced with the current time in the format yymmddHHMMSS when
99
+ the workflow is started. For boolean values, 'true', 'yes', 't', 'y' and 1 are all interpreted as boolean true.
100
+
101
+ All of these Hash keys are optional. Each input variable key and value will be added to the root work item's option Hash.
102
+
103
+ #### Options
104
+
105
+ The option Hash contains special run-time configuration parameters for the workflow:
106
+ * action: String with the action that should be taken. Currently only 'start' is supported. In the future support for
107
+ 'restart' and 'continue' will be added.
108
+ * interactive: Boolean that indicates if the user should be queried to input values for variables that have no value set.
109
+ This will pause the workflow run and is therefore not compatible with scheduling the workflow. For unattended runs the
110
+ options should be set to false, causing the run to throw an exception if an input variable is missing a value.
111
+
112
+ Remaining values are considered to be (default) values for the input variables.
113
+
114
+ #### Run-time configuration
115
+
116
+ The 'run' method takes an optional Hash as argument which will complement and override the options Hash described in the
117
+ previous chapter.
118
+
119
+ Once the workflow is configured and the root work item instantiated, the method will run each top-level task on the root
120
+ work item in sequence until all tasks have completed successfully or a task has failed.
121
+
122
+ ### Work items
123
+
124
+ Creating your own work items is highly recommended and is fairly easy:
125
+
126
+ ```ruby
127
+
128
+ require 'libis/workflow/workitems'
129
+
130
+ class MyWorkItem < ::Libis::Workflow::WorkItem
131
+ attr_accesor :name
132
+
133
+ def initialize
134
+ @name = 'My work item'
135
+ super # Note: this is important as the base class requires some initialization
136
+ end
137
+ end
138
+ ```
139
+
140
+ Work items that are file-based should also include the ::Libis::Workflow::FileItem module:
141
+
142
+ ```ruby
143
+
144
+ require 'libis/workflow/workitems'
145
+
146
+ class MyFileItem < ::Libis::Workflow::WorkItem
147
+ include ::Libis::Workflow::FileItem
148
+
149
+ def initialize(file)
150
+ filename = file
151
+ super
152
+ end
153
+
154
+ def filesize
155
+ properties[:size]
156
+ end
157
+
158
+ def fixity_check(checksum)
159
+ properties[:checksum] == checksum
160
+ end
161
+
162
+ end
163
+ ```
164
+
165
+ ## Tasks
166
+
167
+ Tasks should inherit from ::Libis::Workflow::Task and specify the actions it wants to
168
+ perform on each work item:
169
+
170
+ ```ruby
171
+
172
+ class MyTask < ::Libis::Workflow::Task
173
+
174
+ def process_item(item)
175
+ item.perform_my_action
176
+ rescue Exception => e
177
+ item.set_status(to_status(:failed))
178
+ end
179
+
180
+ end
181
+ ```
182
+
183
+ You have some options to specify the actions:
184
+
185
+ ### Performing an action on each child item of the provided work item
186
+
187
+ In that case the task should provide a 'process_item' method as above. Each child item will be passed as the argument
188
+ to the method and perform whatever needs to be done on the item.
189
+
190
+ If the action fails the method is expected to set the item status field to failed. This is also shown in the previous
191
+ example. If the error is so severe that no other child items should be processed, the action can decide to throw an
192
+ exception, preferably a ::Libis::Workflow::Exception or a child exception thereof.
193
+
194
+ ### Performing an action on the provided work item
195
+
196
+ If the task wants to perform an action on the work item directly, it should define a 'process' method. The work item is
197
+ available to the method as class instance variable 'workitem'. Again the method is responsible to communicate errors
198
+ with a failed status or by throwing an exception.
199
+
200
+ ### Combining both
201
+
202
+ It is possible to perform some action on the parent work item first and then process each child item. Processing the
203
+ child items should be done in process_item as usual, but processing the parent item can be done either by defining a
204
+ pre_process method or a process method that ends with a 'super' call. Using this should be an exception as it is
205
+ recommended to create a seperate task to process the child work items.
206
+
207
+ ### Default behaviour
208
+
209
+ The default implementation of 'process' is to call 'pre_process' and then call 'process_item' on each child item.
210
+
211
+ The default implementation for 'process_item' is to run each child task for each given child item. This will raise an
212
+ exception unless the workflow has defined some sub-tasks for this task. This means that in the workflow definition tree
213
+ each leaf task should either implement it's own 'process_item' method or override the 'process' method. Only non-leaf
214
+ nodes in the workflow definition tree are allowed to use the default implementation (by defining only 'name' and 'tasks'
215
+ value). See above on 'Workflow configuration' for more info.
216
+
217
+ ### Configuration
218
+
219
+ The task takes some options that determine how the task will be handling special cases. The options should be passed to
220
+ the Task constructor as part of the initialization. The workflow configuration will take care of that.
221
+
222
+ * quiet: Boolean - default: false
223
+ * always_run: Boolean - default: false
224
+ * items_first: Boolean - default: false
225
+
226
+ The quiet option surpresses all logging for this task.
227
+
228
+ When the option always_run is set, the task will run even when a previous task failed to run on the item before. Note
229
+ that successfully running such a task will unmark the item as failed. The status history of the item will show which
230
+ tasks failed. Only use this option if you are sure the task will fully recover if the previous tasks failed or did not
231
+ run due to a previous failure.
232
+
233
+ The items_fist option determines the processing order. If a task has multiple subtasks and the given workitem has
234
+ multiple subitems, setting the items_first option will cause it to take the first subitem, run the first subtask on it,
235
+ then the second subtask and so on. Next it will run the first, second, ... subtask on the second subitem and so on. If
236
+ the option is not set or set to false, the first subtask will run on each subitem, then the second subtask on each
237
+ subitem, and so on.
238
+
239
+ ### Convenience functions
240
+
241
+ #### get_root_item()
242
+
243
+ Returns the work item that the workflow started with (and is the root/grand parent of all work items in the ingest run).
244
+
245
+ #### get_work_dir()
246
+
247
+ Returns the work directory as configured for the current ingest run. The work directory can be used as scrap directory
248
+ for creating derived files that can be added as work items to the current flow or for downloading files that will be
249
+ processed later. The work directory is not automaticaly cleaned up, which is considered a task for the workflow implementation.
250
+
251
+ #### capture_cmd(cmd, *args)
252
+
253
+ Allows the task to run an external command-line program and capture it's stdout and stderr output at the same time. The
254
+ first argument is mandatory and should be the command-line program that has to be executed. An arbitrary number of
255
+ command-line arguments may follow.
256
+
257
+ The return value is an array with three elements: the status code returned by the command, the stdout string and the
258
+ stderr string.
259
+
260
+ #### names()
261
+
262
+ An array of strings with the hierarchical path of tasks leading to the current task. Can be usefull for log messages.
263
+
264
+ #### (debug/info/warn/error/fatal)(message, *args)
265
+
266
+ Convenience function for creating log entries. The logger set in ::Libis::Workflow::Config is used to dump log messages.
267
+
268
+ The first argument is mandatory and can be:
269
+ * an integer. The integer is used to look up the message text in ::Libis::Workflow::MessageRegistry.
270
+ * a static string. The message text is used as-is.
271
+ * a string with placement holders as used in String#%. Args can either be an array or a hash. See also Kernel#sprintf.
272
+
273
+ The log message is logged to the general logging and attached to the current work item (workitem) unless another
274
+ work item is passed as first argument after the message.
275
+
276
+ #### check_item_type(klass, item = nil)
277
+
278
+ Checks if the work item is of the given class. 'workitem' is checked if the item argument is not present. If the check
279
+ fails a Runtime exception is thrown which will cause the task to abort if not catched.
280
+
281
+ #### item_type?(klass, item = nil)
282
+
283
+ A less severe variant version of check_item_type which returns a boolean (false if failed).
284
+
285
+ #### to_status(status)
286
+
287
+ Simply prepends the status text with the current task name. The output of this function is typically what the work item
288
+ status field should be set at.
289
+
290
+ ## Contributing
291
+
292
+ 1. Fork it ( https://github.com/libis/workflow/fork )
293
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
294
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
295
+ 4. Push to the branch (`git push origin my-new-feature`)
296
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ desc 'run tests'
7
+ task :default => :spec
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ module Libis
4
+ class WorkflowError < ::RuntimeError
5
+ end
6
+ class WorkflowAbort < ::RuntimeError
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ require 'libis/tools/logger'
2
+
3
+ module Libis
4
+ module Workflow
5
+ module Base
6
+ module Logger
7
+ include ::Libis::Tools::Logger
8
+
9
+ def message(severity, msg, *args)
10
+ item = self.workitem
11
+ item = args.shift if args.size > 0 and args[0].is_a?(WorkItem)
12
+
13
+ item.log_message(severity, to_msg(msg), *args) if item
14
+ end
15
+
16
+ def to_msg(msg)
17
+ case msg
18
+ when String
19
+ {text: msg}
20
+ when Integer
21
+ {id: msg}
22
+ else
23
+ {text: (msg.to_s rescue '')}
24
+ end.merge task: self.namepath
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ require 'fileutils'
4
+
5
+ require 'libis/workflow/workitems/work_item'
6
+
7
+ module Libis
8
+ module Workflow
9
+ module Base
10
+ module Run
11
+ include ::Libis::Workflow::WorkItem
12
+
13
+ def start_date; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
14
+ def start_date=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
15
+
16
+ def tasks; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
17
+ def tasks=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
18
+
19
+ def workflow; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
20
+
21
+ def work_dir
22
+ dir = File.join(Config.workdir, self.name)
23
+ FileUtils.mkpath dir unless Dir.exist?(dir)
24
+ dir
25
+ end
26
+
27
+ def name
28
+ self.workflow.run_name(self.start_date)
29
+ end
30
+
31
+ def names
32
+ Array.new
33
+ end
34
+
35
+ def namepath
36
+ self.name
37
+ end
38
+
39
+ def run(opts = {})
40
+
41
+ self.start_date = Time.now
42
+
43
+ self.options = workflow.prepare_input(self.options.merge(opts))
44
+
45
+ self.tasks = self.workflow.tasks(self)
46
+ configure_tasks self.options
47
+
48
+ self.status = :STARTED
49
+
50
+ self.tasks.each do |task|
51
+ next if self.failed? and not task.options[:allways_run]
52
+ task.run self
53
+ end
54
+
55
+ self.status = :DONE unless self.failed?
56
+
57
+ end
58
+
59
+ protected
60
+
61
+ def configure_tasks(opts)
62
+ self.tasks.each { |task| task.apply_options opts }
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/tools/parameter'
4
+
5
+ module Libis
6
+ module Workflow
7
+ module Base
8
+ module Workflow
9
+
10
+ module ClassMethods
11
+ def require_all
12
+ Config.require_all(File.join(File.dirname(__FILE__), '..', 'tasks'))
13
+ Config.require_all(Config.taskdir)
14
+ Config.require_all(Config.itemdir)
15
+ end
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend ClassMethods
20
+ end
21
+
22
+ def name; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
23
+ def name=(_) ; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
24
+
25
+ def description; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
26
+ def description=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
27
+
28
+ def config; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
29
+ def config=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
30
+
31
+ def configure(cfg)
32
+ self.config.merge! input: {}, tasks: []
33
+ self.config.merge! cfg
34
+ self.name = self.config.delete(:name) || self.class.name
35
+ self.description = self.config.delete(:description) || ''
36
+
37
+ self.class.require_all
38
+
39
+ unless self.config[:tasks].last[:class] && self.config[:tasks].last[:class].split('::').last == 'Analyzer'
40
+ self.config[:tasks] << {class: '::Libis::Workflow::Tasks::Analyzer'}
41
+ end
42
+
43
+ self.config
44
+ end
45
+
46
+ def input
47
+ self.config[:input].inject({}) do |hash, input_def|
48
+ parameter = ::Libis::Tools::Parameter.new input_def.first.to_sym
49
+ input_def.last.each { |k, v| parameter[k] = v}
50
+ hash[input_def.first.to_sym] = parameter
51
+ hash
52
+ end
53
+ end
54
+
55
+ def run_name(timestamp = Time.now)
56
+ "#{self.workflow.name}-#{timestamp.strftime('%Y%m%d%H%M%S')}"
57
+ end
58
+
59
+ def perform(opts = {})
60
+ self.run opts
61
+ end
62
+
63
+ def create_run_object
64
+ self.config[:run_object].constantize.new
65
+ end
66
+
67
+ # @param [Hash] opts
68
+ def run(opts = {})
69
+
70
+ run_object = self.create_run_object
71
+ raise RuntimeError.new "Could not create instance of run object '#{self.config[:run_object]}'" unless run_object
72
+
73
+ run_object.workflow = self
74
+ run_object.options = opts
75
+ run_object.save
76
+
77
+ run_object.run opts
78
+
79
+ run_object
80
+ end
81
+
82
+ # @param [Hash] opts
83
+ def prepare_input(opts)
84
+ options = opts.dup
85
+ self.input.each do |key, parameter|
86
+ key
87
+ # provided in opts
88
+ options[key] = parameter[:default] unless options.has_key? key
89
+ options[key] = parameter.parse(options[key])
90
+ propagate_to = []
91
+ propagate_to = parameter[:propagate_to] if parameter[:propagate_to].is_a? Array
92
+ propagate_to = [parameter[:propagate_to]] if parameter[:propagate_to].is_a? String
93
+ propagate_to.each do |target|
94
+ task_name, param_name = target.split('#')
95
+ param_name ||= key
96
+ options[task_name] ||= {}
97
+ options[task_name][param_name.to_sym] = options[key]
98
+ end
99
+ end
100
+ options
101
+ end
102
+
103
+ def tasks(parent = nil)
104
+ self.config[:tasks].map do |cfg|
105
+ instantize_task(parent || self, cfg)
106
+ end
107
+ end
108
+
109
+ def instantize_task(parent, cfg)
110
+ task_class = Task
111
+ task_class = cfg[:class].constantize if cfg[:class]
112
+ # noinspection RubyArgCount
113
+ task_instance = task_class.new(parent, cfg)
114
+ cfg[:tasks].map do |task_cfg|
115
+ task_instance << instantize_task(task_instance, task_cfg)
116
+ end rescue nil
117
+ task_instance
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end