bahuvrihi-tap 0.10.6 → 0.10.7
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rap +13 -5
- data/lib/tap.rb +0 -4
- data/lib/tap/app.rb +10 -138
- data/lib/tap/constants.rb +1 -1
- data/lib/tap/declarations.rb +201 -0
- data/lib/tap/env.rb +10 -2
- data/lib/tap/exe.rb +2 -2
- data/lib/tap/generator/generators/root/templates/Rakefile +1 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
- data/lib/tap/spec.rb +38 -63
- data/lib/tap/spec/adapter.rb +8 -31
- data/lib/tap/spec/inheritable_class_test_root.rb +9 -0
- data/lib/tap/support/audit.rb +0 -2
- data/lib/tap/support/combinator.rb +83 -31
- data/lib/tap/support/configurable_class.rb +7 -7
- data/lib/tap/support/{dependable.rb → dependencies.rb} +9 -7
- data/lib/tap/support/executable.rb +226 -19
- data/lib/tap/support/gems/rake.rb +6 -3
- data/lib/tap/support/join.rb +87 -0
- data/lib/tap/support/joins.rb +13 -0
- data/lib/tap/support/joins/fork.rb +18 -0
- data/lib/tap/support/joins/merge.rb +20 -0
- data/lib/tap/support/joins/sequence.rb +21 -0
- data/lib/tap/support/joins/switch.rb +23 -0
- data/lib/tap/support/joins/sync_merge.rb +57 -0
- data/lib/tap/support/lazydoc/document.rb +10 -0
- data/lib/tap/support/node.rb +58 -0
- data/lib/tap/support/parser.rb +379 -0
- data/lib/tap/support/schema.rb +350 -0
- data/lib/tap/support/tdoc.rb +5 -0
- data/lib/tap/support/validation.rb +2 -0
- data/lib/tap/task.rb +26 -61
- data/lib/tap/test.rb +9 -82
- data/lib/tap/test/assertions.rb +38 -0
- data/lib/tap/test/extensions.rb +78 -0
- data/lib/tap/test/{file_methods.rb → file_test.rb} +64 -37
- data/lib/tap/test/{file_methods_class.rb → file_test_class.rb} +2 -2
- data/lib/tap/test/regexp_escape.rb +87 -0
- data/lib/tap/test/script_test.rb +46 -0
- data/lib/tap/test/script_tester.rb +109 -0
- data/lib/tap/test/{subset_methods.rb → subset_test.rb} +9 -9
- data/lib/tap/test/{subset_methods_class.rb → subset_test_class.rb} +22 -14
- data/lib/tap/test/{tap_methods.rb → tap_test.rb} +43 -6
- data/lib/tap/test/utils.rb +6 -3
- metadata +27 -24
- data/lib/tap/parser.rb +0 -619
- data/lib/tap/spec/file_methods.rb +0 -16
- data/lib/tap/spec/file_methods_class.rb +0 -13
- data/lib/tap/spec/subset_methods.rb +0 -14
- data/lib/tap/support/batchable.rb +0 -47
- data/lib/tap/support/batchable_class.rb +0 -107
- data/lib/tap/support/declarations.rb +0 -131
- data/lib/tap/support/lazydoc/declaration.rb +0 -20
- data/lib/tap/support/parsers/base.rb +0 -81
- data/lib/tap/support/parsers/server.rb +0 -113
- data/lib/tap/test/script_methods.rb +0 -75
- data/lib/tap/test/script_methods/regexp_escape.rb +0 -94
- data/lib/tap/test/script_methods/script_test.rb +0 -109
- data/lib/tap/workflow.rb +0 -161
@@ -0,0 +1,20 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
module Joins
|
4
|
+
|
5
|
+
class Merge < ReverseJoin
|
6
|
+
def join(target, sources)
|
7
|
+
sources.each do |source|
|
8
|
+
# merging can use the existing audit trails... each distinct
|
9
|
+
# input is getting sent to one place (the target)
|
10
|
+
complete(source) do |_result|
|
11
|
+
yield(_result) if block_given?
|
12
|
+
enq(target, _result)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
module Joins
|
4
|
+
|
5
|
+
class Sequence < Join
|
6
|
+
def join(source, targets)
|
7
|
+
current_task = source
|
8
|
+
targets.each do |next_task|
|
9
|
+
# simply pass results from one task to the next.
|
10
|
+
complete(current_task) do |_result|
|
11
|
+
yield(_result) if block_given?
|
12
|
+
enq(next_task, _result)
|
13
|
+
end
|
14
|
+
current_task = next_task
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
module Joins
|
4
|
+
|
5
|
+
class Switch < Join
|
6
|
+
def join(source, targets)
|
7
|
+
complete(source) do |_result|
|
8
|
+
if index = yield(_result)
|
9
|
+
unless target = targets[index]
|
10
|
+
raise "no switch target for index: #{index}"
|
11
|
+
end
|
12
|
+
|
13
|
+
enq(target, _result)
|
14
|
+
else
|
15
|
+
source.app.aggregator.store(_result)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
module Joins
|
4
|
+
|
5
|
+
class SyncMerge < ReverseJoin
|
6
|
+
def join(target, sources)
|
7
|
+
|
8
|
+
# a hash of (source, index) pairs where index is the
|
9
|
+
# index of the source in a combination
|
10
|
+
indicies = {}
|
11
|
+
|
12
|
+
# a hash of (source, combinations) pairs where combinations
|
13
|
+
# are combination arrays that the source participates in.
|
14
|
+
# note that in unbatched mode, some sources may not
|
15
|
+
# participate in any combinations.
|
16
|
+
combinations = {}
|
17
|
+
|
18
|
+
sets = sources.collect {|source| unbatched ? [source] : source.batch }
|
19
|
+
Support::Combinator.new(*sets).each do |combo|
|
20
|
+
combination = Array.new(combo.length, nil)
|
21
|
+
|
22
|
+
combo.each do |source|
|
23
|
+
indicies[source] ||= combo.index(source)
|
24
|
+
(combinations[source] ||= []) << combination
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
sources.each_with_index do |source, index|
|
29
|
+
complete(source) do |_result|
|
30
|
+
src = _result._current_source
|
31
|
+
|
32
|
+
source_index = indicies[src]
|
33
|
+
(combinations[src] ||= []).each do |combination|
|
34
|
+
if combination[source_index] != nil
|
35
|
+
raise "sync_merge collision... already got a result for #{src}"
|
36
|
+
end
|
37
|
+
|
38
|
+
combination[source_index] = _result
|
39
|
+
unless combination.include?(nil)
|
40
|
+
# merge the source audits
|
41
|
+
_merge_result = Support::Audit.merge(*combination)
|
42
|
+
|
43
|
+
yield(_merge_result) if block_given?
|
44
|
+
enq(target, _merge_result)
|
45
|
+
|
46
|
+
# reset the group array
|
47
|
+
combination.collect! {|i| nil }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -62,6 +62,16 @@ module Tap
|
|
62
62
|
def [](const_name)
|
63
63
|
const_attrs[const_name] ||= {}
|
64
64
|
end
|
65
|
+
|
66
|
+
# Returns an array of the const_names in self with at
|
67
|
+
# least one attribute.
|
68
|
+
def const_names
|
69
|
+
names = []
|
70
|
+
const_attrs.each_pair do |const_name, attrs|
|
71
|
+
names << const_name unless attrs.empty?
|
72
|
+
end
|
73
|
+
names
|
74
|
+
end
|
65
75
|
|
66
76
|
# Register the specified line number to self. Returns a
|
67
77
|
# comment_class instance corresponding to the line.
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# Represents a task node in a Schema.
|
5
|
+
class Node
|
6
|
+
|
7
|
+
# An array of arguments used to instantiate
|
8
|
+
# the node, and to specify arguments enqued
|
9
|
+
# to the instance (when the node is directly
|
10
|
+
# enqued to a round... see input)
|
11
|
+
attr_accessor :argv
|
12
|
+
|
13
|
+
# The input or source for the node. Inputs
|
14
|
+
# may be a Join, nil, or an Integer (indicating
|
15
|
+
# the node should be enqued to a round, with
|
16
|
+
# inputs as specified in argv).
|
17
|
+
attr_accessor :input
|
18
|
+
|
19
|
+
# The output for the node. Output may be a
|
20
|
+
# a Join or nil.
|
21
|
+
attr_accessor :output
|
22
|
+
|
23
|
+
def initialize(argv=[], input=nil, output=nil)
|
24
|
+
@argv = argv
|
25
|
+
@input = input
|
26
|
+
@output = output
|
27
|
+
end
|
28
|
+
|
29
|
+
# Resets the source and join to nil.
|
30
|
+
def globalize
|
31
|
+
self.input = nil
|
32
|
+
self.output = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# True if the input and output are nil.
|
36
|
+
def global?
|
37
|
+
input == nil && output == nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the round for self; a round is indicated
|
41
|
+
# by an integer input. If input is anything but
|
42
|
+
# an integer, round returns nil.
|
43
|
+
def round
|
44
|
+
input.kind_of?(Integer) ? input : nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Alias for input=
|
48
|
+
def round=(input)
|
49
|
+
self.input = input
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<#{self.class}:#{object_id} argv=[#{argv.join(' ')}] input=#{input.inspect} output=#{output.inspect}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
require 'tap/support/schema'
|
2
|
+
autoload(:Shellwords, 'shellwords')
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
module Support
|
6
|
+
|
7
|
+
# ==== Round Assignment
|
8
|
+
# Tasks can be defined and set to a round using the following:
|
9
|
+
#
|
10
|
+
# break assigns task(s) to round
|
11
|
+
# -- next 0
|
12
|
+
# --+ next 1
|
13
|
+
# --++ next 2
|
14
|
+
# --+2 next 2
|
15
|
+
# --+2[1,2,3] 1,2,3 2
|
16
|
+
#
|
17
|
+
# Here all task (except c) are parsed into round 0, then the
|
18
|
+
# final argument reassigns e to round 3.
|
19
|
+
#
|
20
|
+
# schema = Parser.new("a -- b --+ c -- d -- e --+3[4]").schema
|
21
|
+
# schema.rounds(true) # => [[0,1,3],[2], nil, [4]]
|
22
|
+
#
|
23
|
+
# ==== Workflow Assignment
|
24
|
+
# All simple workflow patterns except switch can be specified within
|
25
|
+
# the parse syntax (switch is the exception because there is no good
|
26
|
+
# way to define a block from an array).
|
27
|
+
#
|
28
|
+
# break pattern source(s) target(s)
|
29
|
+
# --: sequence last next
|
30
|
+
# --[] fork last next
|
31
|
+
# --{} merge next last
|
32
|
+
# --() sync_merge next last
|
33
|
+
#
|
34
|
+
# example meaning
|
35
|
+
# --1:2 1.sequence(2)
|
36
|
+
# --1:2:3 1.sequence(2,3)
|
37
|
+
# --:2: last.sequence(2,next)
|
38
|
+
# --[] last.fork(next)
|
39
|
+
# --1{2,3,4} 1.merge(2,3,4)
|
40
|
+
# --(2,3,4) last.sync_merge(2,3,4)
|
41
|
+
#
|
42
|
+
# Note how all of the bracketed styles behave similarly; they are
|
43
|
+
# parsed with essentially the same code, only reversing the source
|
44
|
+
# and target in the case of merges.
|
45
|
+
#
|
46
|
+
# Here a and b are sequenced inline. Task c is assigned to no
|
47
|
+
# workflow until the final argument which sequenced b and c.
|
48
|
+
#
|
49
|
+
# schema = Parser.new("a --: b -- c --1:2i").schema
|
50
|
+
# schema.argvs # => [["a"], ["b"], ["c"], []]
|
51
|
+
# schema.joins(true) # => [[:sequence,0,[1],{}], [:sequence,1,[2],{:iterate => true}]]
|
52
|
+
#
|
53
|
+
# ==== Globals
|
54
|
+
# Global instances of task (used, for example, by dependencies) may
|
55
|
+
# be assigned in the parse syntax as well. The break for a global
|
56
|
+
# is '--*'.
|
57
|
+
#
|
58
|
+
# schema = Parser.new("a -- b --* global_name --config for --global").schema
|
59
|
+
# schema.globals(true) # => [2]
|
60
|
+
#
|
61
|
+
# ==== Escapes and End Flags
|
62
|
+
# Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
|
63
|
+
# any number of arguments may be enclosed within the escape. After the
|
64
|
+
# end delimiter, breaks are active once again.
|
65
|
+
#
|
66
|
+
# schema = Parser.new("a -- b -- c").schema
|
67
|
+
# schema.argvs # => [["a"], ["b"], ["c"]]
|
68
|
+
#
|
69
|
+
# schema = Parser.new("a -. -- b .- -- c").schema
|
70
|
+
# schema.argvs # => [["a", "--", "b"], ["c"]]
|
71
|
+
#
|
72
|
+
# Parsing continues until the end of argv, or a an end flag '---' is
|
73
|
+
# reached. The end flag may also be escaped.
|
74
|
+
#
|
75
|
+
# schema = Parser.new("a -- b --- c").schema
|
76
|
+
# schema.argvs # => [["a"], ["b"]]
|
77
|
+
#
|
78
|
+
class Parser
|
79
|
+
|
80
|
+
# A set of parsing routines used internally by Tap::Support::Parser,
|
81
|
+
# modularized for ease of testing, and potential re-use. These methods
|
82
|
+
# require that <tt>current_index</tt> and <tt>previous_index</tt> be
|
83
|
+
# implemented in the including class.
|
84
|
+
module Utils
|
85
|
+
module_function
|
86
|
+
|
87
|
+
# Defines a break regexp that matches a bracketed-pairs
|
88
|
+
# break. The left and right brackets are specified as
|
89
|
+
# inputs. After a match:
|
90
|
+
#
|
91
|
+
# $1:: The source string after the break.
|
92
|
+
# (ex: '[]' => '', '1[]' => '1')
|
93
|
+
# $2:: The target string.
|
94
|
+
# (ex: '[]' => '', '1[1,2,3]' => '1,2,3')
|
95
|
+
# $3:: The modifier string.
|
96
|
+
# (ex: '[]i' => 'i', '1[1,2,3]is' => 'is')
|
97
|
+
#
|
98
|
+
def bracket_regexp(l, r)
|
99
|
+
/\A(\d*)#{Regexp.escape(l)}([\d,]*)#{Regexp.escape(r)}([A-z]*)\z/
|
100
|
+
end
|
101
|
+
|
102
|
+
# The escape begin argument
|
103
|
+
ESCAPE_BEGIN = "-."
|
104
|
+
|
105
|
+
# The escape end argument
|
106
|
+
ESCAPE_END = ".-"
|
107
|
+
|
108
|
+
# The parser end flag
|
109
|
+
END_FLAG = "---"
|
110
|
+
|
111
|
+
# Matches any breaking arg (ex: '--', '--+', '--1:2')
|
112
|
+
# After the match:
|
113
|
+
#
|
114
|
+
# $1:: The string after the break
|
115
|
+
# (ex: '--' => '', '--++' => '++', '--1(2,3)' => '1(2,3)')
|
116
|
+
#
|
117
|
+
BREAK = /\A--(\z|[\+\d\:\*\[\{\(].*\z)/
|
118
|
+
|
119
|
+
# Matches an execution-round break. After the match:
|
120
|
+
#
|
121
|
+
# $2:: The round string, or nil.
|
122
|
+
# (ex: '' => nil, '++' => '++', '+1' => '+1')
|
123
|
+
# $5:: The target string, or nil.
|
124
|
+
# (ex: '+' => nil, '+[1,2,3]' => '1,2,3')
|
125
|
+
#
|
126
|
+
ROUND = /\A((\+(\d*|\+*))(\[([\d,]*)\])?)?\z/
|
127
|
+
|
128
|
+
# Matches a sequence break. After the match:
|
129
|
+
#
|
130
|
+
# $1:: The sequence string after the break.
|
131
|
+
# (ex: ':' => ':', '1:2' => '1:2', '1:' => '1:', ':2' => ':2')
|
132
|
+
# $3:: The modifier string.
|
133
|
+
# (ex: ':i' => 'i', '1:2is' => 'is')
|
134
|
+
#
|
135
|
+
SEQUENCE = /\A(\d*(:\d*)+)([A-z]*)\z/
|
136
|
+
|
137
|
+
# Matches an instance break. After the match:
|
138
|
+
#
|
139
|
+
# $1:: The index string after the break.
|
140
|
+
# (ex: '*' => '', '*1' => '1')
|
141
|
+
#
|
142
|
+
INSTANCE = /\A\*(\d*)\z/
|
143
|
+
|
144
|
+
# A break regexp using "[]"
|
145
|
+
FORK = bracket_regexp("[", "]")
|
146
|
+
|
147
|
+
# A break regexp using "{}"
|
148
|
+
MERGE = bracket_regexp("{", "}")
|
149
|
+
|
150
|
+
# A break regexp using "()"
|
151
|
+
SYNC_MERGE = bracket_regexp("(", ")")
|
152
|
+
|
153
|
+
# Parses an indicies str along commas, and collects the indicies
|
154
|
+
# as integers. Ex:
|
155
|
+
#
|
156
|
+
# parse_indicies('') # => []
|
157
|
+
# parse_indicies('1') # => [1]
|
158
|
+
# parse_indicies('1,2,3') # => [1,2,3]
|
159
|
+
#
|
160
|
+
def parse_indicies(str, regexp=/,+/)
|
161
|
+
indicies = []
|
162
|
+
str.split(regexp).each do |n|
|
163
|
+
indicies << n.to_i unless n.empty?
|
164
|
+
end
|
165
|
+
indicies
|
166
|
+
end
|
167
|
+
|
168
|
+
# Parses the match of a ROUND regexp into a round index
|
169
|
+
# and an array of task indicies that should be added to the
|
170
|
+
# round. The inputs correspond to $2 and $5 for the match.
|
171
|
+
#
|
172
|
+
# If $2 is nil, a round index of zero is assumed; if $5 is
|
173
|
+
# nil or empty, then indicies of [:current_index] are assumed.
|
174
|
+
#
|
175
|
+
# parse_round("+", "") # => [1, [:current_index]]
|
176
|
+
# parse_round("+2", "1,2,3") # => [2, [1,2,3]]
|
177
|
+
# parse_round(nil, nil) # => [0, [:current_index]]
|
178
|
+
#
|
179
|
+
def parse_round(two, five)
|
180
|
+
index = case two
|
181
|
+
when nil then 0
|
182
|
+
when /\d/ then two[1, two.length-1].to_i
|
183
|
+
else two.length
|
184
|
+
end
|
185
|
+
[index, five.to_s.empty? ? [current_index] : parse_indicies(five)]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Parses the match of a SEQUENCE regexp into an [indicies, options]
|
189
|
+
# array. The inputs corresponds to $1 and $3 for the match. The
|
190
|
+
# previous and current index are assumed if $1 starts and/or ends
|
191
|
+
# with a semi-colon.
|
192
|
+
#
|
193
|
+
# parse_sequence("1:2:3", '') # => [[1,2,3], {}]
|
194
|
+
# parse_sequence(":1:2:", '') # => [[:previous_index,1,2,:current_index], {}]
|
195
|
+
#
|
196
|
+
def parse_sequence(one, three)
|
197
|
+
seq = parse_indicies(one, /:+/)
|
198
|
+
seq.unshift previous_index if one[0] == ?:
|
199
|
+
seq << current_index if one[-1] == ?:
|
200
|
+
[seq, parse_options(three)]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Parses the match of an INSTANCE regexp into an index.
|
204
|
+
# The input corresponds to $1 for the match. The current
|
205
|
+
# index is assumed if $1 is empty.
|
206
|
+
#
|
207
|
+
# parse_instance("1") # => 1
|
208
|
+
# parse_instance("") # => :current_index
|
209
|
+
#
|
210
|
+
def parse_instance(one)
|
211
|
+
one.empty? ? current_index : one.to_i
|
212
|
+
end
|
213
|
+
|
214
|
+
# Parses the match of an bracket_regexp into a [source_index,
|
215
|
+
# target_indicies, options] array. The inputs corresponds to $1,
|
216
|
+
# $2, and $3 for the match. The previous and current index are
|
217
|
+
# assumed if $1 and/or $2 is empty.
|
218
|
+
#
|
219
|
+
# parse_bracket("1", "2,3", "") # => [1, [2,3], {}]
|
220
|
+
# parse_bracket("", "", "") # => [:previous_index, [:current_index], {}]
|
221
|
+
# parse_bracket("1", "", "") # => [1, [:current_index], {}]
|
222
|
+
# parse_bracket("", "2,3", "") # => [:previous_index, [2,3], {}]
|
223
|
+
#
|
224
|
+
def parse_bracket(one, two, three)
|
225
|
+
targets = parse_indicies(two)
|
226
|
+
targets << current_index if targets.empty?
|
227
|
+
[one.empty? ? previous_index : one.to_i, targets, parse_options(three)]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Parses an options string into a hash. The input corresponds
|
231
|
+
# to $3 in a SEQUENCE or bracket_regexp match. Raises an error
|
232
|
+
# if the options string contains unknown options.
|
233
|
+
#
|
234
|
+
# parse_options("") # => {}
|
235
|
+
# parse_options("is") # => {:iterate => true, :stack => true}
|
236
|
+
#
|
237
|
+
def parse_options(three)
|
238
|
+
options = {}
|
239
|
+
0.upto(three.length - 1) do |char_index|
|
240
|
+
char = three[char_index, 1]
|
241
|
+
unless index = Join::SHORT_FLAGS.index(char)
|
242
|
+
raise "unknown option in: #{three} (#{char})"
|
243
|
+
end
|
244
|
+
|
245
|
+
options[Join::FLAGS[index]] = true
|
246
|
+
end
|
247
|
+
options
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
include Utils
|
252
|
+
|
253
|
+
# The schema into which nodes are parsed
|
254
|
+
attr_reader :schema
|
255
|
+
|
256
|
+
def initialize(argv=[])
|
257
|
+
@current_index = 0
|
258
|
+
@schema = Schema.new
|
259
|
+
parse(argv)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Iterates through the argv splitting out task and workflow definitions.
|
263
|
+
# Task definitions are split out (with configurations) along round and/or
|
264
|
+
# workflow break lines. Rounds and workflows are dynamically parsed;
|
265
|
+
# tasks may be reassigned to different rounds or workflows by later
|
266
|
+
# arguments.
|
267
|
+
#
|
268
|
+
# Parse is non-destructive to argv. If a string argv is provided, parse
|
269
|
+
# splits it into an array using Shellwords.
|
270
|
+
#
|
271
|
+
def parse(argv)
|
272
|
+
parse!(argv.kind_of?(String) ? argv : argv.dup)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Same as parse, but removes parsed args from argv.
|
276
|
+
def parse!(argv)
|
277
|
+
argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
|
278
|
+
argv.unshift('--')
|
279
|
+
|
280
|
+
escape = false
|
281
|
+
current_argv = schema[current_index].argv
|
282
|
+
|
283
|
+
while !argv.empty?
|
284
|
+
arg = argv.shift
|
285
|
+
|
286
|
+
# if escaping, add escaped arguments
|
287
|
+
# until an escape-end argument
|
288
|
+
if escape
|
289
|
+
if arg == ESCAPE_END
|
290
|
+
escape = false
|
291
|
+
else
|
292
|
+
current_argv << arg
|
293
|
+
end
|
294
|
+
|
295
|
+
next
|
296
|
+
end
|
297
|
+
|
298
|
+
case arg
|
299
|
+
when ESCAPE_BEGIN
|
300
|
+
# begin escaping if indicated
|
301
|
+
escape = true
|
302
|
+
|
303
|
+
when END_FLAG
|
304
|
+
# break on an end-flag
|
305
|
+
break
|
306
|
+
|
307
|
+
when BREAK
|
308
|
+
# a breaking argument was reached:
|
309
|
+
# unless the current argv is empty,
|
310
|
+
# append and start a new definition
|
311
|
+
unless current_argv.empty?
|
312
|
+
self.current_index += 1
|
313
|
+
current_argv = schema[current_index].argv
|
314
|
+
end
|
315
|
+
|
316
|
+
# parse the break string for any
|
317
|
+
# schema modifications
|
318
|
+
parse_break($1)
|
319
|
+
|
320
|
+
else
|
321
|
+
# add all other non-breaking args to
|
322
|
+
# the current argv; this includes
|
323
|
+
# both inputs and configurations
|
324
|
+
current_argv << arg
|
325
|
+
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
schema
|
330
|
+
end
|
331
|
+
|
332
|
+
def load(argv)
|
333
|
+
argv.each do |arg|
|
334
|
+
case arg
|
335
|
+
when Array
|
336
|
+
schema.nodes << arg
|
337
|
+
self.current_index += 1
|
338
|
+
else
|
339
|
+
parse_break(arg)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
schema
|
344
|
+
end
|
345
|
+
|
346
|
+
protected
|
347
|
+
|
348
|
+
# The index of the node currently being parsed.
|
349
|
+
attr_accessor :current_index
|
350
|
+
|
351
|
+
# Returns current_index-1, or raises an error if current_index < 1.
|
352
|
+
def previous_index
|
353
|
+
raise ArgumentError, 'there is no previous index' if current_index < 1
|
354
|
+
current_index - 1
|
355
|
+
end
|
356
|
+
|
357
|
+
# determines the type of break and modifies self appropriately
|
358
|
+
def parse_break(arg) # :nodoc:
|
359
|
+
case arg
|
360
|
+
when ROUND
|
361
|
+
round, indicies = parse_round($2, $5)
|
362
|
+
indicies.each {|index| schema[index].round = round }
|
363
|
+
|
364
|
+
when SEQUENCE
|
365
|
+
indicies, options = parse_sequence($1, $3)
|
366
|
+
while indicies.length > 1
|
367
|
+
schema.set(Joins::Sequence, indicies.shift, indicies[0], options)
|
368
|
+
end
|
369
|
+
|
370
|
+
when INSTANCE then schema[parse_instance($1)].globalize
|
371
|
+
when FORK then schema.set(Joins::Fork, *parse_bracket($1, $2, $3))
|
372
|
+
when MERGE then schema.set(Joins::Merge, *parse_bracket($1, $2, $3))
|
373
|
+
when SYNC_MERGE then schema.set(Joins::SyncMerge, *parse_bracket($1, $2, $3))
|
374
|
+
else raise ArgumentError, "invalid break argument: #{arg}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|