trailblazer-activity 0.3.2 → 0.4.o
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +4 -3
- data/CHANGES.md +4 -0
- data/lib/trailblazer/activity.rb +82 -140
- data/lib/trailblazer/activity/config.rb +37 -0
- data/lib/trailblazer/activity/dsl/add_task.rb +22 -0
- data/lib/trailblazer/activity/dsl/helper.rb +49 -0
- data/lib/trailblazer/activity/implementation/build_state.rb +31 -0
- data/lib/trailblazer/activity/implementation/fast_track.rb +14 -0
- data/lib/trailblazer/activity/implementation/interface.rb +16 -0
- data/lib/trailblazer/activity/implementation/path.rb +55 -0
- data/lib/trailblazer/activity/implementation/railway.rb +18 -0
- data/lib/trailblazer/activity/{introspection.rb → introspect.rb} +33 -11
- data/lib/trailblazer/activity/magnetic.rb +7 -18
- data/lib/trailblazer/activity/magnetic/builder.rb +37 -92
- data/lib/trailblazer/activity/magnetic/builder/default_normalizer.rb +26 -0
- data/lib/trailblazer/activity/magnetic/builder/fast_track.rb +13 -15
- data/lib/trailblazer/activity/magnetic/builder/normalizer.rb +105 -0
- data/lib/trailblazer/activity/magnetic/builder/path.rb +14 -15
- data/lib/trailblazer/activity/magnetic/builder/railway.rb +8 -11
- data/lib/trailblazer/activity/magnetic/dsl.rb +4 -1
- data/lib/trailblazer/activity/magnetic/finalizer.rb +1 -1
- data/lib/trailblazer/activity/magnetic/merge.rb +18 -0
- data/lib/trailblazer/activity/present.rb +1 -1
- data/lib/trailblazer/activity/schema/dependencies.rb +6 -1
- data/lib/trailblazer/activity/state.rb +58 -0
- data/lib/trailblazer/activity/structures.rb +1 -2
- data/lib/trailblazer/activity/subprocess.rb +6 -3
- data/lib/trailblazer/activity/task_builder.rb +38 -0
- data/lib/trailblazer/activity/task_wrap.rb +46 -0
- data/lib/trailblazer/{wrap → activity/task_wrap}/call_task.rb +3 -3
- data/lib/trailblazer/activity/task_wrap/merge.rb +23 -0
- data/lib/trailblazer/{wrap → activity/task_wrap}/runner.rb +14 -16
- data/lib/trailblazer/{wrap → activity/task_wrap}/trace.rb +3 -3
- data/lib/trailblazer/activity/trace.rb +22 -28
- data/lib/trailblazer/activity/version.rb +2 -2
- data/lib/trailblazer/circuit.rb +7 -5
- data/trailblazer-activity.gemspec +2 -1
- metadata +39 -14
- data/lib/trailblazer/activity/heritage.rb +0 -30
- data/lib/trailblazer/activity/magnetic/builder/block.rb +0 -37
- data/lib/trailblazer/activity/process.rb +0 -16
- data/lib/trailblazer/activity/wrap.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7eb2fd05fd42b86db598839e4903e7f81cd44f43f435625a2907c689df522675
|
|
4
|
+
data.tar.gz: f83b35f3c13b44f11f36eec569fbcb008f5681a935ce078316648dd7036a7e88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1b5afb1f62768f177a77dece7954d0251df6f2568373e134aa54aa4535998bba270795ffc200b00bd6b5cb892237452bf0799f492d35cc8ad43fe0e13f83714
|
|
7
|
+
data.tar.gz: 0e6c7a8abb6b7e5db35ac1a154389eed2c6462e6fce1fcd8d502edf4cfd8484037a1c769b096add34a952b74ca1eac4630861d245c4c955d5d8528f343b6d0c7
|
data/.travis.yml
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
language: ruby
|
|
3
3
|
rvm:
|
|
4
|
-
- 2.0
|
|
4
|
+
# - 2.0
|
|
5
5
|
- 2.1
|
|
6
6
|
- 2.2
|
|
7
7
|
- 2.3.3
|
|
8
|
-
- 2.4.
|
|
8
|
+
- 2.4.1
|
|
9
|
+
- 2.5.0
|
|
9
10
|
matrix:
|
|
10
11
|
include:
|
|
11
|
-
- rvm: jruby-9.1.
|
|
12
|
+
- rvm: jruby-9.1.15.0
|
|
12
13
|
env: JRUBY_OPTS="--profile.api"
|
|
13
14
|
before_install: gem install bundler
|
data/CHANGES.md
CHANGED
data/lib/trailblazer/activity.rb
CHANGED
|
@@ -1,177 +1,119 @@
|
|
|
1
|
-
require "trailblazer/
|
|
1
|
+
require "trailblazer/activity/version"
|
|
2
2
|
|
|
3
3
|
module Trailblazer
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@process.instance_variable_get(:@circuit).to_fields
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def debug # TODO: TEST ME
|
|
11
|
-
@debug
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
extend Interface
|
|
16
|
-
|
|
17
|
-
require "trailblazer/activity/version"
|
|
18
|
-
require "trailblazer/activity/structures"
|
|
19
|
-
|
|
20
|
-
require "trailblazer/activity/subprocess"
|
|
21
|
-
|
|
22
|
-
require "trailblazer/activity/wrap"
|
|
23
|
-
require "trailblazer/wrap/call_task"
|
|
24
|
-
require "trailblazer/wrap/trace"
|
|
25
|
-
require "trailblazer/wrap/runner"
|
|
26
|
-
|
|
27
|
-
require "trailblazer/activity/trace"
|
|
28
|
-
require "trailblazer/activity/present"
|
|
29
|
-
|
|
4
|
+
def self.Activity(implementation=Activity::Path, options={})
|
|
5
|
+
Activity.new(implementation, state)
|
|
6
|
+
end
|
|
30
7
|
|
|
31
|
-
|
|
32
|
-
|
|
8
|
+
class Activity < Module
|
|
9
|
+
attr_reader :initial_state
|
|
33
10
|
|
|
34
|
-
|
|
35
|
-
|
|
11
|
+
def initialize(implementation, options)
|
|
12
|
+
builder, adds, circuit, outputs, options = BuildState.build_state_for( implementation.config, options)
|
|
36
13
|
|
|
37
|
-
|
|
14
|
+
@initial_state = State::Config.build(
|
|
15
|
+
builder: builder,
|
|
16
|
+
options: options,
|
|
17
|
+
adds: adds,
|
|
18
|
+
circuit: circuit,
|
|
19
|
+
outputs: outputs,
|
|
20
|
+
)
|
|
38
21
|
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
include *options[:extend] # include the DSL methods.
|
|
23
|
+
include PublicAPI
|
|
41
24
|
end
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# FIXME: move to Introspection
|
|
48
|
-
def self.find(&block)
|
|
49
|
-
@process.instance_variable_get(:@circuit).instance_variable_get(:@map).find(&block)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def self.outputs
|
|
53
|
-
@outputs
|
|
26
|
+
# Injects the initial configuration into the module defining a new activity.
|
|
27
|
+
def extended(extended)
|
|
28
|
+
super
|
|
29
|
+
extended.instance_variable_set(:@state, initial_state)
|
|
54
30
|
end
|
|
55
31
|
|
|
56
|
-
#- DSL part
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
module Inspect
|
|
34
|
+
def inspect
|
|
35
|
+
"#<Trailblazer::Activity: {#{name || self[:options][:name]}}>"
|
|
36
|
+
end
|
|
60
37
|
end
|
|
61
38
|
|
|
62
|
-
private
|
|
63
39
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def self.initialize!(builder_class, normalizer)
|
|
71
|
-
initialize_activity_dsl!(builder_class, normalizer)
|
|
72
|
-
recompile_process!
|
|
73
|
-
end
|
|
40
|
+
require "trailblazer/activity/dsl/helper"
|
|
41
|
+
# Helpers such as Path, Output, End to be included into {Activity}.
|
|
42
|
+
module DSLHelper
|
|
43
|
+
extend Forwardable
|
|
44
|
+
def_delegators :@builder, :Path
|
|
45
|
+
def_delegators DSL::Helper, :Output, :End
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@debug = {} # only @adds and @debug are mutable
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def self.recompile_process!
|
|
82
|
-
@process, @outputs = Recompile.( @adds )
|
|
47
|
+
def Path(*args, &block)
|
|
48
|
+
self[:builder].Path(*args, &block)
|
|
49
|
+
end
|
|
83
50
|
end
|
|
84
51
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
52
|
+
module Accessor
|
|
53
|
+
def []=(*args)
|
|
54
|
+
@state = State::Config.send(:[]=, @state, *args)
|
|
55
|
+
end
|
|
88
56
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# DISCUSS: make this functions and don't include?
|
|
92
|
-
module DSL
|
|
93
|
-
|
|
94
|
-
# Create a new method (e.g. Activity::step) that delegates to its builder, recompiles
|
|
95
|
-
# the process, etc. Method comes in a module so it can be overridden via modules.
|
|
96
|
-
#
|
|
97
|
-
# This approach assumes you maintain a @adds and a @debug instance variable. and #heritage
|
|
98
|
-
def self.def_dsl!(_name)
|
|
99
|
-
Module.new do
|
|
100
|
-
define_method(_name) do |*args, &block|
|
|
101
|
-
_task(_name, *args, &block) # TODO: similar to Block.
|
|
102
|
-
end
|
|
103
|
-
end
|
|
57
|
+
def [](*args)
|
|
58
|
+
State::Config[@state, *args]
|
|
104
59
|
end
|
|
60
|
+
end
|
|
105
61
|
|
|
106
|
-
|
|
62
|
+
# FIXME: still to be decided
|
|
63
|
+
# By including those modules, we create instance methods.
|
|
64
|
+
# Later, this module is `extended` in Path, Railway and FastTrack, and
|
|
65
|
+
# imports the DSL methods as class methods.
|
|
66
|
+
module PublicAPI
|
|
67
|
+
include Accessor
|
|
107
68
|
|
|
108
|
-
|
|
109
|
-
|
|
69
|
+
require "trailblazer/activity/dsl/add_task"
|
|
70
|
+
include DSL::AddTask
|
|
110
71
|
|
|
111
|
-
|
|
72
|
+
require "trailblazer/activity/implementation/interface"
|
|
73
|
+
include Activity::Interface # DISCUSS
|
|
112
74
|
|
|
113
|
-
|
|
114
|
-
@adds.freeze
|
|
75
|
+
include DSLHelper # DISCUSS
|
|
115
76
|
|
|
116
|
-
|
|
77
|
+
include Activity::Inspect # DISCUSS
|
|
117
78
|
|
|
118
|
-
|
|
79
|
+
require "trailblazer/activity/magnetic/merge"
|
|
80
|
+
include Magnetic::Merge # Activity#merge!
|
|
119
81
|
|
|
120
|
-
|
|
121
|
-
|
|
82
|
+
def call(args, argumenter: [], **circuit_options) # DISCUSS: the argumenter logic might be moved out.
|
|
83
|
+
_, args, circuit_options = argumenter.inject( [self, args, circuit_options] ) { |memo, argumenter| argumenter.(*memo) }
|
|
122
84
|
|
|
123
|
-
|
|
124
|
-
@debug[task] = { id: local_options[:id] }.freeze
|
|
85
|
+
self[:circuit].( args, circuit_options.merge(argumenter: argumenter) )
|
|
125
86
|
end
|
|
126
87
|
end
|
|
88
|
+
end # Activity
|
|
89
|
+
end
|
|
127
90
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
extend Forwardable # TODO: test those helpers
|
|
132
|
-
def_delegators :@builder, :Path#, :task
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
extend DSL # _task, :add_introspection
|
|
136
|
-
extend DSL.def_dsl!(:task) # define Activity::task.
|
|
137
|
-
|
|
138
|
-
extend Heritage::Accessor
|
|
91
|
+
require "trailblazer/circuit"
|
|
92
|
+
require "trailblazer/activity/structures"
|
|
93
|
+
require "trailblazer/activity/config"
|
|
139
94
|
|
|
95
|
+
require "trailblazer/activity/implementation/build_state"
|
|
96
|
+
require "trailblazer/activity/implementation/interface"
|
|
97
|
+
require "trailblazer/activity/implementation/path"
|
|
98
|
+
require "trailblazer/activity/implementation/railway"
|
|
99
|
+
require "trailblazer/activity/implementation/fast_track"
|
|
140
100
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
outputs = recompile_outputs(end_events)
|
|
101
|
+
require "trailblazer/activity/task_wrap"
|
|
102
|
+
require "trailblazer/activity/task_wrap/call_task"
|
|
103
|
+
require "trailblazer/activity/task_wrap/trace"
|
|
104
|
+
require "trailblazer/activity/task_wrap/runner"
|
|
105
|
+
require "trailblazer/activity/task_wrap/merge"
|
|
147
106
|
|
|
148
|
-
|
|
149
|
-
|
|
107
|
+
require "trailblazer/activity/trace"
|
|
108
|
+
require "trailblazer/activity/present"
|
|
150
109
|
|
|
151
|
-
|
|
110
|
+
require "trailblazer/activity/introspect"
|
|
152
111
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[
|
|
156
|
-
semantic = evt.instance_variable_get(:@options)[:semantic], # DISCUSS: better API here?
|
|
157
|
-
Activity::Output(evt, semantic)
|
|
158
|
-
]
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
::Hash[ ary ]
|
|
162
|
-
end
|
|
163
|
-
end
|
|
112
|
+
# require "trailblazer/activity/heritage"
|
|
113
|
+
require "trailblazer/activity/subprocess"
|
|
164
114
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return Magnetic::Builder::Railway, Magnetic::Builder::DefaultNormalizer.new(plus_poles: Magnetic::Builder::Railway.default_plus_poles)
|
|
169
|
-
end
|
|
115
|
+
require "trailblazer/activity/state"
|
|
116
|
+
require "trailblazer/activity/magnetic" # the "magnetic" DSL
|
|
117
|
+
require "trailblazer/activity/schema/sequence"
|
|
170
118
|
|
|
171
|
-
|
|
172
|
-
extend DSL.def_dsl!(:step)
|
|
173
|
-
extend DSL.def_dsl!(:fail)
|
|
174
|
-
extend DSL.def_dsl!(:pass)
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
end
|
|
119
|
+
require "trailblazer/activity/magnetic/builder/normalizer" # DISCUSS: name and location are odd. This one uses Activity ;)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Trailblazer
|
|
2
|
+
module Activity::State
|
|
3
|
+
# Compile-time
|
|
4
|
+
#
|
|
5
|
+
# DISCUSS: we could replace parts with Hamster::Hash.
|
|
6
|
+
class Config
|
|
7
|
+
def self.build(variables={})
|
|
8
|
+
Hash[ variables.collect { |k,v| [k, v.freeze] } ].freeze
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.[]=(state, *args)
|
|
12
|
+
if args.size == 2
|
|
13
|
+
key, value = *args
|
|
14
|
+
|
|
15
|
+
state = state.merge(key => value)
|
|
16
|
+
else
|
|
17
|
+
directive, key, value = *args
|
|
18
|
+
|
|
19
|
+
state = state.merge( directive => {}.freeze ) unless state.key?(directive)
|
|
20
|
+
|
|
21
|
+
directive_hash = state[directive].merge(key => value)
|
|
22
|
+
state = state.merge( directive => directive_hash.freeze )
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
state
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.[](state, *args)
|
|
29
|
+
directive, key = *args
|
|
30
|
+
|
|
31
|
+
return state[directive] if args.size == 1
|
|
32
|
+
return state[directive][key] if state.key?(directive)
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Trailblazer::Activity < Module
|
|
2
|
+
module DSL
|
|
3
|
+
module AddTask
|
|
4
|
+
def add_task!(name, task, options, &block)
|
|
5
|
+
# The beautiful thing about State.add is it doesn't mutate anything.
|
|
6
|
+
# We're changing state here, on the outside, by overriding the ivars.
|
|
7
|
+
# That in turn means, the only mutated entity is this module.
|
|
8
|
+
|
|
9
|
+
_builder, adds, circuit, outputs, options = State.add( self[:builder], self[:adds], name, task, options, &block ) # this could be an extension itself.
|
|
10
|
+
|
|
11
|
+
self[:adds] = adds
|
|
12
|
+
self[:circuit] = circuit
|
|
13
|
+
self[:outputs] = outputs
|
|
14
|
+
|
|
15
|
+
task, local_options = options
|
|
16
|
+
|
|
17
|
+
# {Extension API} call all extensions.
|
|
18
|
+
local_options[:extension].collect { |ext| ext.(self, *options) } if local_options[:extension]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Trailblazer
|
|
2
|
+
module Activity::DSL
|
|
3
|
+
# Create a new method (e.g. Activity::step) that delegates to its builder, recompiles
|
|
4
|
+
# the circuit, etc. Method comes in a module so it can be overridden via modules.
|
|
5
|
+
#
|
|
6
|
+
# This approach assumes you maintain a {#add_task!} method.
|
|
7
|
+
def self.def_dsl(_name)
|
|
8
|
+
Module.new do
|
|
9
|
+
define_method(_name) do |task, options={}, &block|
|
|
10
|
+
builder, adds, circuit, outputs, options = add_task!(_name, task, options, &block) # TODO: similar to Block.
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Shortcut functions for the DSL. These have no state.
|
|
16
|
+
module Helper
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Output( Left, :failure )
|
|
20
|
+
# Output( :failure ) #=> Output::Semantic
|
|
21
|
+
def Output(signal, semantic=nil)
|
|
22
|
+
return Activity::Magnetic::DSL::Output::Semantic.new(signal) if semantic.nil?
|
|
23
|
+
|
|
24
|
+
Activity.Output(signal, semantic)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def End(name, semantic)
|
|
28
|
+
Activity.End(name, semantic)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def Path(normalizer, track_color: "track_#{rand}", end_semantic: :success, **options)
|
|
32
|
+
options = options.merge(track_color: track_color, end_semantic: end_semantic)
|
|
33
|
+
|
|
34
|
+
# Build an anonymous class which will be where the block is evaluated in.
|
|
35
|
+
# We use the same normalizer here, so DSL calls in the inner block have the same behavior.
|
|
36
|
+
path = Module.new do
|
|
37
|
+
extend Activity::Path( options.merge( normalizer: normalizer ) )
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# this block is called in DSL::ProcessTuples. This could be improved somehow.
|
|
41
|
+
->(block) {
|
|
42
|
+
path.instance_exec(&block)
|
|
43
|
+
|
|
44
|
+
[ track_color, path ]
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class Trailblazer::Activity < Module
|
|
2
|
+
module BuildState
|
|
3
|
+
# Compute all objects that need to be passed into the new Activity module.
|
|
4
|
+
# 1. Build the normalizer (unless passed with :normalizer)
|
|
5
|
+
# 2. Build the builder (in State)
|
|
6
|
+
# 3. Let State compute all state variables (that implies recompiling the Process)
|
|
7
|
+
#
|
|
8
|
+
# @return [Builder, Adds, Process, Outputs, remaining options]
|
|
9
|
+
# @api private
|
|
10
|
+
def self.build_state_for(default_options, options)
|
|
11
|
+
options = default_options.merge(options) # TODO: use Variables::Merge() here.
|
|
12
|
+
normalizer, options = build_normalizer(options)
|
|
13
|
+
builder, adds, circuit, outputs, options = build_state(normalizer, options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Builds the normalizer (to process options in DSL calls) unless {:normalizer} is already set.
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
def self.build_normalizer(normalizer_class:, normalizer: false, **options)
|
|
20
|
+
normalizer, options = normalizer_class.build( options ) unless normalizer
|
|
21
|
+
|
|
22
|
+
return normalizer, options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.build_state(normalizer, builder_class:, builder_options: {}, **options)
|
|
26
|
+
builder, adds, circuit, outputs = State.build(builder_class, normalizer, options.merge(builder_options))
|
|
27
|
+
|
|
28
|
+
return builder, adds, circuit, outputs, options
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Trailblazer::Activity < Module
|
|
2
|
+
def self.FastTrack(options={})
|
|
3
|
+
FastTrack.new(FastTrack, options)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# Implementation module that can be passed to `Activity[]`.
|
|
7
|
+
class FastTrack < Trailblazer::Activity
|
|
8
|
+
def self.config
|
|
9
|
+
Railway.config.merge(
|
|
10
|
+
builder_class: Magnetic::Builder::FastTrack,
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|