trailblazer-activity 0.3.2 → 0.4.o

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +4 -3
  3. data/CHANGES.md +4 -0
  4. data/lib/trailblazer/activity.rb +82 -140
  5. data/lib/trailblazer/activity/config.rb +37 -0
  6. data/lib/trailblazer/activity/dsl/add_task.rb +22 -0
  7. data/lib/trailblazer/activity/dsl/helper.rb +49 -0
  8. data/lib/trailblazer/activity/implementation/build_state.rb +31 -0
  9. data/lib/trailblazer/activity/implementation/fast_track.rb +14 -0
  10. data/lib/trailblazer/activity/implementation/interface.rb +16 -0
  11. data/lib/trailblazer/activity/implementation/path.rb +55 -0
  12. data/lib/trailblazer/activity/implementation/railway.rb +18 -0
  13. data/lib/trailblazer/activity/{introspection.rb → introspect.rb} +33 -11
  14. data/lib/trailblazer/activity/magnetic.rb +7 -18
  15. data/lib/trailblazer/activity/magnetic/builder.rb +37 -92
  16. data/lib/trailblazer/activity/magnetic/builder/default_normalizer.rb +26 -0
  17. data/lib/trailblazer/activity/magnetic/builder/fast_track.rb +13 -15
  18. data/lib/trailblazer/activity/magnetic/builder/normalizer.rb +105 -0
  19. data/lib/trailblazer/activity/magnetic/builder/path.rb +14 -15
  20. data/lib/trailblazer/activity/magnetic/builder/railway.rb +8 -11
  21. data/lib/trailblazer/activity/magnetic/dsl.rb +4 -1
  22. data/lib/trailblazer/activity/magnetic/finalizer.rb +1 -1
  23. data/lib/trailblazer/activity/magnetic/merge.rb +18 -0
  24. data/lib/trailblazer/activity/present.rb +1 -1
  25. data/lib/trailblazer/activity/schema/dependencies.rb +6 -1
  26. data/lib/trailblazer/activity/state.rb +58 -0
  27. data/lib/trailblazer/activity/structures.rb +1 -2
  28. data/lib/trailblazer/activity/subprocess.rb +6 -3
  29. data/lib/trailblazer/activity/task_builder.rb +38 -0
  30. data/lib/trailblazer/activity/task_wrap.rb +46 -0
  31. data/lib/trailblazer/{wrap → activity/task_wrap}/call_task.rb +3 -3
  32. data/lib/trailblazer/activity/task_wrap/merge.rb +23 -0
  33. data/lib/trailblazer/{wrap → activity/task_wrap}/runner.rb +14 -16
  34. data/lib/trailblazer/{wrap → activity/task_wrap}/trace.rb +3 -3
  35. data/lib/trailblazer/activity/trace.rb +22 -28
  36. data/lib/trailblazer/activity/version.rb +2 -2
  37. data/lib/trailblazer/circuit.rb +7 -5
  38. data/trailblazer-activity.gemspec +2 -1
  39. metadata +39 -14
  40. data/lib/trailblazer/activity/heritage.rb +0 -30
  41. data/lib/trailblazer/activity/magnetic/builder/block.rb +0 -37
  42. data/lib/trailblazer/activity/process.rb +0 -16
  43. data/lib/trailblazer/activity/wrap.rb +0 -22
@@ -1,5 +1,5 @@
1
- class Trailblazer::Activity
2
- module Wrap
1
+ class Trailblazer::Activity < Module
2
+ module TaskWrap
3
3
  # TaskWrap step that calls the actual wrapped task and passes all `original_args` to it.
4
4
  #
5
5
  # It writes to wrap_ctx[:return_signal], wrap_ctx[:return_args]
@@ -13,7 +13,7 @@ class Trailblazer::Activity
13
13
  # DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
14
14
  wrap_ctx = wrap_ctx.merge( return_signal: return_signal, return_args: return_args )
15
15
 
16
- [ Right, [ wrap_ctx, original_args ], **circuit_options ]
16
+ return Right, [ wrap_ctx, original_args ]
17
17
  end
18
18
  end # Wrap
19
19
  end
@@ -0,0 +1,23 @@
1
+ module Trailblazer
2
+ module Activity::TaskWrap
3
+ # This is instantiated via the DSL, and passed to the :extension API,
4
+ # allowing to add steps to the Activity's static_wrap.
5
+ # Compile-time function
6
+ class Merge
7
+ def initialize(extension_plan)
8
+ @extension_plan = extension_plan
9
+ end
10
+
11
+ # {:extension API}
12
+ def call(activity, task, local_options, *returned_options)
13
+ # we could make the default initial_activity injectable via the DSL, the value would sit in returned_options or local_options.
14
+ static_wrap = Activity::TaskWrap.wrap_static_for(task, wrap_static: activity[:wrap_static] || {})
15
+
16
+ # # macro might want to apply changes to the static task_wrap (e.g. Inject)
17
+ new_wrap = Activity::Path::Plan.merge( static_wrap, @extension_plan )
18
+
19
+ activity[:wrap_static, task] = new_wrap
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
- class Trailblazer::Activity
2
- module Wrap
1
+ class Trailblazer::Activity < Module
2
+ module TaskWrap
3
3
  # The runner is passed into Activity#call( runner: Runner ) and is called for every task in the circuit.
4
4
  # It runs the TaskWrap per task.
5
5
  #
@@ -9,16 +9,15 @@ class Trailblazer::Activity
9
9
  #
10
10
  # @api private
11
11
  # @interface Runner
12
- def self.call(task, args, wrap_runtime: raise, wrap_static: raise, **circuit_options)
12
+ def self.call(task, args, **circuit_options)
13
13
  wrap_ctx = { task: task }
14
14
 
15
15
  # this activity is "wrapped around" the actual `task`.
16
- task_wrap_activity = apply_wirings(task, wrap_static, wrap_runtime)
16
+ task_wrap_activity = merge_static_with_runtime(task, circuit_options)
17
17
 
18
18
  # We save all original args passed into this Runner.call, because we want to return them later after this wrap
19
19
  # is finished.
20
- original_args = [ args, circuit_options.merge( wrap_runtime: wrap_runtime, wrap_static: wrap_static ) ]
21
-
20
+ original_args = [ args, circuit_options ]
22
21
  # call the wrap {Activity} around the task.
23
22
  wrap_end_signal, ( wrap_ctx, _ ) = task_wrap_activity.(
24
23
  [ wrap_ctx, original_args ] # we omit circuit_options here on purpose, so the wrapping activity uses the default, plain Runner.
@@ -27,7 +26,7 @@ class Trailblazer::Activity
27
26
  # don't return the wrap's end signal, but the one from call_task.
28
27
  # return all original_args for the next "real" task in the circuit (this includes circuit_options).
29
28
 
30
- [ wrap_ctx[:return_signal], wrap_ctx[:return_args] ]
29
+ return wrap_ctx[:return_signal], wrap_ctx[:return_args]
31
30
  end
32
31
 
33
32
  private
@@ -36,19 +35,18 @@ class Trailblazer::Activity
36
35
  #
37
36
  # NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
38
37
  # unnecessary computations at `call`-time since steps might not even be executed.
39
- def self.apply_wirings(task, wrap_static, wrap_runtime)
40
- wrap_activity = wrap_static[task] # find static wrap for this specific task, or default wrap activity.
38
+ # TODO: make this faster.
39
+ def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
40
+ wrap_activity = TaskWrap.wrap_static_for(task, circuit_options) # find static wrap for this specific task, or default wrap activity.
41
41
 
42
42
  # Apply runtime alterations.
43
43
  # Grab the additional wirings for the particular `task` from `wrap_runtime` (returns default otherwise).
44
-
45
- # NOTE: the recompilation is absolutely not necessary at runtime and could be avoided if the runtime wrap is empty.
46
- # TODO: make this faster.
47
- adds = Trailblazer::Activity::Magnetic::Builder.merge(wrap_activity, wrap_runtime[task])
48
- wrap_activity, outputs = Magnetic::Builder::Finalizer.(adds)
49
-
50
- wrap_activity
44
+ wrap_runtime[task] ? Trailblazer::Activity::Path::Plan.merge(wrap_activity, wrap_runtime[task]) : wrap_activity
51
45
  end
52
46
  end # Runner
47
+
48
+ def self.wrap_static_for(task, wrap_static:, default_activity: TaskWrap.initial_activity, **)
49
+ wrap_static[task] || default_activity
50
+ end
53
51
  end
54
52
  end
@@ -1,5 +1,5 @@
1
- class Trailblazer::Activity
2
- module Wrap
1
+ class Trailblazer::Activity < Module
2
+ module TaskWrap
3
3
  # TaskWrap tasks for tracing.
4
4
  module Trace
5
5
  # def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
@@ -8,7 +8,7 @@ class Trailblazer::Activity
8
8
 
9
9
  original_flow_options[:stack].indent!
10
10
 
11
- original_flow_options[:stack] << Trailblazer::Activity::Trace::Entity.new( wrap_config[:task], :args, nil, {}, original_circuit_options[:introspection] )
11
+ original_flow_options[:stack] << Trailblazer::Activity::Trace::Entity.new( wrap_config[:task], :args, nil, {}, original_circuit_options[:introspect] )
12
12
 
13
13
  [ Trailblazer::Activity::Right, [wrap_config, original_args], **circuit_options ]
14
14
  end
@@ -1,6 +1,5 @@
1
1
  module Trailblazer
2
- class Activity
3
- # Trace#call will call the activities and trace what steps are called, options passed,
2
+ class Activity < Module # Trace#call will call the activities and trace what steps are called, options passed,
4
3
  # and the order and nesting.
5
4
  #
6
5
  # stack, _ = Trailblazer::Activity::Trace.(activity, activity[:Start], { id: 1 })
@@ -8,49 +7,44 @@ module Trailblazer
8
7
  #
9
8
  # Hooks into the TaskWrap.
10
9
  module Trace
11
- def self.call(activity, (options), *args, &block)
10
+ # {:argumenter} API
11
+ # FIXME: needs Introspect.arguments_for_call
12
+ # FIXME: needs TaskWrap.arguments_for_call
13
+ def self.arguments_for_call(activity, (options, flow_options), **circuit_options)
12
14
  tracing_flow_options = {
13
15
  stack: Trace::Stack.new,
14
16
  }
15
17
 
16
18
  tracing_circuit_options = {
17
- runner: Wrap::Runner,
18
- wrap_runtime: ::Hash.new(Trace.wirings), # FIXME: this still overrides existing wrap_runtimes.
19
- wrap_static: ::Hash.new( Trailblazer::Activity::Wrap.initial_activity ), # FIXME
20
- introspection: compute_debug(activity), # FIXME: this is still also set in Activity::call
19
+ wrap_runtime: ::Hash.new(Trace.wirings), # FIXME: this still overrides existing :wrap_runtime.
21
20
  }
22
21
 
23
- last_signal, (options, flow_options) = call_activity( activity, [ options, tracing_flow_options ], tracing_circuit_options, &block )
24
- # tracing_flow_options.merge(flow_options),
25
-
26
- return flow_options[:stack].to_a, last_signal, options, flow_options
22
+ return activity, [ options, flow_options.merge(tracing_flow_options) ], circuit_options.merge(tracing_circuit_options)
27
23
  end
28
24
 
29
- private
25
+ def self.call(activity, (options, flow_options), *args)
26
+ activity, (options, flow_options), circuit_options = Trace.arguments_for_call( activity, [options, flow_options], {} ) # only run once for the entire circuit!
27
+ last_signal, (options, flow_options) =
28
+ activity.(
29
+ [options, flow_options],
30
+ circuit_options.merge({ argumenter: [ Introspect.method(:arguments_for_call), TaskWrap.method(:arguments_for_call) ] })
31
+ )
30
32
 
31
- # TODO: test alterations with any wrap_circuit.
32
- def self.call_activity(activity, *args, &block)
33
- return activity.(*args) unless block
34
- block.(activity, *args)
33
+ return flow_options[:stack].to_a, last_signal, [options, flow_options]
35
34
  end
36
35
 
37
- # TODO: this is experimental.
38
- # Go through all nested Activities and grab their `Activity.debug` field. This gets all merged into
39
- # one big debugging hash, instead of computing it overly complex at runtime and while executing the circuit.
40
- def self.compute_debug(activity)
41
- arrs = Introspect.collect( activity, recursive: true ) { |task, _| task }.find_all { |task| task.is_a?(Interface) }.collect { |task| task.debug }.flatten(1).compact
42
-
43
- arrs.inject( activity.debug ) { |memo, debug| memo.merge(debug) }
44
- end
36
+ private
45
37
 
46
38
  # Insertions for the trace tasks that capture the arguments just before calling the task,
47
39
  # and before the TaskWrap is finished.
48
40
  #
49
- # Note that the TaskWrap steps are implemented in Activity::Wrap::Trace.
41
+ # Note that the TaskWrap steps are implemented in Activity::TaskWrap::Trace.
50
42
  def self.wirings
51
- Activity::Magnetic::Builder::Path.plan do
52
- task Wrap::Trace.method(:capture_args), id: "task_wrap.capture_args", before: "task_wrap.call_task"
53
- task Wrap::Trace.method(:capture_return), id: "task_wrap.capture_return", before: "End.success", group: :end
43
+ Module.new do
44
+ extend Activity::Path::Plan()
45
+
46
+ task TaskWrap::Trace.method(:capture_args), id: "task_wrap.capture_args", before: "task_wrap.call_task"
47
+ task TaskWrap::Trace.method(:capture_return), id: "task_wrap.capture_return", before: "End.success", group: :end
54
48
  end
55
49
  end
56
50
 
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
- class Activity
3
- VERSION = "0.3.2"
2
+ class Activity < Module
3
+ VERSION = "0.4.o"
4
4
  end
5
5
  end
@@ -12,9 +12,11 @@ module Trailblazer
12
12
  # @see Activity
13
13
  # @api semi-private
14
14
  class Circuit
15
- def initialize(map, stop_events)
15
+ def initialize(map, stop_events, name: nil, start_task: map.keys.first)
16
16
  @map = map
17
17
  @stop_events = stop_events
18
+ @name = name
19
+ @start_task = start_task
18
20
  end
19
21
 
20
22
  # @param args [Array] all arguments to be passed to the task's `call`
@@ -32,7 +34,7 @@ module Trailblazer
32
34
  # @return [last_signal, options, flow_options, *args]
33
35
  #
34
36
  # DISCUSS: returned circuit_options are ignored when calling the runner.
35
- def call(args, task: raise, runner: Run, **circuit_options)
37
+ def call(args, task: @start_task, runner: Run, **circuit_options)
36
38
  loop do
37
39
  last_signal, args, _ignored_circuit_options = runner.(
38
40
  task,
@@ -45,14 +47,14 @@ module Trailblazer
45
47
 
46
48
  task = next_for(task, last_signal) do |next_task, in_map|
47
49
  raise IllegalInputError.new("#{task}") unless in_map
48
- raise IllegalOutputSignalError.new("from : `#{task}`===>[ #{last_signal.inspect} ]") unless next_task
50
+ raise IllegalOutputSignalError.new("#{@name}[#{task}][ #{last_signal.inspect} ]") unless next_task
49
51
  end
50
52
  end
51
53
  end
52
54
 
53
55
  # Returns the circuit's components.
54
- def to_fields
55
- [ @map, @stop_events ]
56
+ def decompose
57
+ return @map, @stop_events
56
58
  end
57
59
 
58
60
  private
@@ -21,11 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.require_paths = ["lib"]
22
22
 
23
23
  spec.add_dependency "hirb"
24
+ spec.add_dependency "trailblazer-context"
24
25
 
25
26
  spec.add_development_dependency "bundler", "~> 1.14"
26
27
  spec.add_development_dependency "rake", "~> 10.0"
27
28
  spec.add_development_dependency "minitest", "~> 5.0"
28
29
  spec.add_development_dependency "trailblazer-test"
29
30
 
30
- spec.required_ruby_version = '>= 2.0.0'
31
+ spec.required_ruby_version = '>= 2.1.0'
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-activity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.o
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-23 00:00:00.000000000 Z
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hirb
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: trailblazer-context
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -100,12 +114,20 @@ files:
100
114
  - Rakefile
101
115
  - lib/trailblazer-activity.rb
102
116
  - lib/trailblazer/activity.rb
103
- - lib/trailblazer/activity/heritage.rb
104
- - lib/trailblazer/activity/introspection.rb
117
+ - lib/trailblazer/activity/config.rb
118
+ - lib/trailblazer/activity/dsl/add_task.rb
119
+ - lib/trailblazer/activity/dsl/helper.rb
120
+ - lib/trailblazer/activity/implementation/build_state.rb
121
+ - lib/trailblazer/activity/implementation/fast_track.rb
122
+ - lib/trailblazer/activity/implementation/interface.rb
123
+ - lib/trailblazer/activity/implementation/path.rb
124
+ - lib/trailblazer/activity/implementation/railway.rb
125
+ - lib/trailblazer/activity/introspect.rb
105
126
  - lib/trailblazer/activity/magnetic.rb
106
127
  - lib/trailblazer/activity/magnetic/builder.rb
107
- - lib/trailblazer/activity/magnetic/builder/block.rb
128
+ - lib/trailblazer/activity/magnetic/builder/default_normalizer.rb
108
129
  - lib/trailblazer/activity/magnetic/builder/fast_track.rb
130
+ - lib/trailblazer/activity/magnetic/builder/normalizer.rb
109
131
  - lib/trailblazer/activity/magnetic/builder/path.rb
110
132
  - lib/trailblazer/activity/magnetic/builder/railway.rb
111
133
  - lib/trailblazer/activity/magnetic/dsl.rb
@@ -113,19 +135,22 @@ files:
113
135
  - lib/trailblazer/activity/magnetic/dsl/plus_poles.rb
114
136
  - lib/trailblazer/activity/magnetic/finalizer.rb
115
137
  - lib/trailblazer/activity/magnetic/generate.rb
138
+ - lib/trailblazer/activity/magnetic/merge.rb
116
139
  - lib/trailblazer/activity/present.rb
117
- - lib/trailblazer/activity/process.rb
118
140
  - lib/trailblazer/activity/schema/dependencies.rb
119
141
  - lib/trailblazer/activity/schema/sequence.rb
142
+ - lib/trailblazer/activity/state.rb
120
143
  - lib/trailblazer/activity/structures.rb
121
144
  - lib/trailblazer/activity/subprocess.rb
145
+ - lib/trailblazer/activity/task_builder.rb
146
+ - lib/trailblazer/activity/task_wrap.rb
147
+ - lib/trailblazer/activity/task_wrap/call_task.rb
148
+ - lib/trailblazer/activity/task_wrap/merge.rb
149
+ - lib/trailblazer/activity/task_wrap/runner.rb
150
+ - lib/trailblazer/activity/task_wrap/trace.rb
122
151
  - lib/trailblazer/activity/trace.rb
123
152
  - lib/trailblazer/activity/version.rb
124
- - lib/trailblazer/activity/wrap.rb
125
153
  - lib/trailblazer/circuit.rb
126
- - lib/trailblazer/wrap/call_task.rb
127
- - lib/trailblazer/wrap/runner.rb
128
- - lib/trailblazer/wrap/trace.rb
129
154
  - trailblazer-activity.gemspec
130
155
  homepage: http://trailblazer.to/gems/workflow
131
156
  licenses:
@@ -139,15 +164,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
164
  requirements:
140
165
  - - ">="
141
166
  - !ruby/object:Gem::Version
142
- version: 2.0.0
167
+ version: 2.1.0
143
168
  required_rubygems_version: !ruby/object:Gem::Requirement
144
169
  requirements:
145
- - - ">="
170
+ - - ">"
146
171
  - !ruby/object:Gem::Version
147
- version: '0'
172
+ version: 1.3.1
148
173
  requirements: []
149
174
  rubyforge_project:
150
- rubygems_version: 2.6.8
175
+ rubygems_version: 2.7.3
151
176
  signing_key:
152
177
  specification_version: 4
153
178
  summary: The main element for Trailblazer's BPMN-compliant workflows.
@@ -1,30 +0,0 @@
1
- module Trailblazer
2
- # This is copied from the Declarative gem. This might get removed in favor of a real heritage gem.
3
- class Activity
4
- class Heritage < Array
5
- # Record inheritable assignments for replay in an inheriting class.
6
- def record(method, *args, &block)
7
- self << { method: method, args: args, block: block }
8
- end
9
-
10
- # Replay the recorded assignments on inheritor.
11
- # Accepts a block that will allow processing the arguments for every recorded statement.
12
- def call(inheritor, &block)
13
- each { |cfg| call!(inheritor, cfg, &block) }
14
- end
15
-
16
- private
17
- def call!(inheritor, cfg)
18
- yield cfg if block_given? # allow messing around with recorded arguments.
19
-
20
- inheritor.send(cfg[:method], *cfg[:args], &cfg[:block])
21
- end
22
-
23
- module Accessor
24
- def heritage
25
- @heritage ||= Heritage.new
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,37 +0,0 @@
1
- module Trailblazer
2
- module Activity::Magnetic
3
- # Mutable DSL object.
4
- #
5
- # This is the only object mutated when building the ADDS, and it's only
6
- # used when using block-syntax, such as
7
- # Path.plan do
8
- # # ..
9
- # end
10
- class Builder::Block
11
- def initialize(builder)
12
- @builder = builder
13
- @adds = [] # mutable
14
- end
15
-
16
- # Evaluate user's block and return the new ADDS.
17
- # Used in Builder::plan or in nested DSL calls.
18
- def call(&block)
19
- instance_exec(&block)
20
- @adds
21
- end
22
-
23
- [:task, :step, :pass, :fail].each do |name| # create :step, :pass, :task, etc.
24
- define_method(name) { |*args, &block| capture_adds(name, *args, &block) } # def step(..) => forward to builder, track ADDS
25
- end
26
-
27
- extend Forwardable
28
- def_delegators :@builder, :Output, :Path, :End # TODO: make this official.
29
-
30
- # #task, #step, etc. are called via the immutable builder.
31
- def capture_adds(name, *args, &block)
32
- adds, *returned_options = @builder.send(name, *args, &block)
33
- @adds += adds
34
- end
35
- end
36
- end
37
- end
@@ -1,16 +0,0 @@
1
- module Trailblazer
2
- class Activity::Process
3
- # The executable run-time instance for an Activity.
4
- def initialize(circuit_hash, end_events)
5
- @default_start_event = circuit_hash.keys.first
6
- @circuit = Circuit.new(circuit_hash, end_events)
7
- end
8
-
9
- def call(args, task: @default_start_event, **circuit_options)
10
- @circuit.(
11
- args,
12
- circuit_options.merge( task: task ) , # this passes :runner to the {Circuit}.
13
- )
14
- end
15
- end
16
- end