tap 0.12.4 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +34 -0
- data/README +62 -41
- data/bin/tap +36 -40
- data/cmd/console.rb +14 -6
- data/cmd/manifest.rb +62 -58
- data/cmd/run.rb +49 -31
- data/doc/API +84 -0
- data/doc/Class Reference +83 -115
- data/doc/Examples/Command Line +36 -0
- data/doc/Examples/Workflow +40 -0
- data/lib/tap/app.rb +293 -214
- data/lib/tap/app/node.rb +43 -0
- data/lib/tap/app/queue.rb +77 -0
- data/lib/tap/app/stack.rb +16 -0
- data/lib/tap/app/state.rb +22 -0
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/env.rb +400 -314
- data/lib/tap/env/constant.rb +227 -0
- data/lib/tap/env/gems.rb +63 -0
- data/lib/tap/env/manifest.rb +89 -0
- data/lib/tap/env/minimap.rb +292 -0
- data/lib/tap/{support → env}/string_ext.rb +2 -2
- data/lib/tap/exe.rb +113 -125
- data/lib/tap/join.rb +175 -0
- data/lib/tap/joins.rb +9 -0
- data/lib/tap/joins/switch.rb +44 -0
- data/lib/tap/joins/sync.rb +99 -0
- data/lib/tap/root.rb +100 -491
- data/lib/tap/root/utils.rb +220 -0
- data/lib/tap/{support → root}/versions.rb +31 -29
- data/lib/tap/schema.rb +248 -0
- data/lib/tap/schema/parser.rb +413 -0
- data/lib/tap/schema/utils.rb +82 -0
- data/lib/tap/support/intern.rb +19 -6
- data/lib/tap/support/templater.rb +8 -3
- data/lib/tap/task.rb +175 -171
- data/lib/tap/tasks/dump.rb +58 -0
- data/lib/tap/tasks/load.rb +62 -0
- metadata +30 -73
- data/cmd/destroy.rb +0 -27
- data/cmd/generate.rb +0 -27
- data/doc/Command Reference +0 -105
- data/doc/Syntax Reference +0 -234
- data/doc/Tutorial +0 -348
- data/lib/tap/dump.rb +0 -142
- data/lib/tap/file_task.rb +0 -384
- data/lib/tap/generator/arguments.rb +0 -13
- data/lib/tap/generator/base.rb +0 -176
- data/lib/tap/generator/destroy.rb +0 -60
- data/lib/tap/generator/generate.rb +0 -93
- data/lib/tap/generator/generators/command/command_generator.rb +0 -21
- data/lib/tap/generator/generators/command/templates/command.erb +0 -32
- data/lib/tap/generator/generators/config/config_generator.rb +0 -98
- data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
- data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
- data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
- data/lib/tap/generator/generators/root/root_generator.rb +0 -84
- data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
- data/lib/tap/generator/generators/root/templates/README +0 -14
- data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
- data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
- data/lib/tap/generator/generators/root/templates/gemspec +0 -27
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
- data/lib/tap/generator/generators/task/task_generator.rb +0 -25
- data/lib/tap/generator/generators/task/templates/task.erb +0 -14
- data/lib/tap/generator/generators/task/templates/test.erb +0 -19
- data/lib/tap/generator/manifest.rb +0 -20
- data/lib/tap/generator/preview.rb +0 -69
- data/lib/tap/load.rb +0 -64
- data/lib/tap/spec.rb +0 -41
- data/lib/tap/support/aggregator.rb +0 -65
- data/lib/tap/support/audit.rb +0 -333
- data/lib/tap/support/constant.rb +0 -143
- data/lib/tap/support/constant_manifest.rb +0 -126
- data/lib/tap/support/dependencies.rb +0 -54
- data/lib/tap/support/dependency.rb +0 -44
- data/lib/tap/support/executable.rb +0 -198
- data/lib/tap/support/executable_queue.rb +0 -125
- data/lib/tap/support/gems.rb +0 -43
- data/lib/tap/support/join.rb +0 -144
- data/lib/tap/support/joins.rb +0 -12
- data/lib/tap/support/joins/switch.rb +0 -27
- data/lib/tap/support/joins/sync_merge.rb +0 -38
- data/lib/tap/support/manifest.rb +0 -171
- data/lib/tap/support/minimap.rb +0 -90
- data/lib/tap/support/node.rb +0 -176
- data/lib/tap/support/parser.rb +0 -450
- data/lib/tap/support/schema.rb +0 -385
- data/lib/tap/support/shell_utils.rb +0 -67
- data/lib/tap/test.rb +0 -77
- data/lib/tap/test/assertions.rb +0 -38
- data/lib/tap/test/env_vars.rb +0 -29
- data/lib/tap/test/extensions.rb +0 -73
- data/lib/tap/test/file_test.rb +0 -362
- data/lib/tap/test/file_test_class.rb +0 -15
- data/lib/tap/test/regexp_escape.rb +0 -87
- data/lib/tap/test/script_test.rb +0 -46
- data/lib/tap/test/script_tester.rb +0 -115
- data/lib/tap/test/subset_test.rb +0 -260
- data/lib/tap/test/subset_test_class.rb +0 -99
- data/lib/tap/test/tap_test.rb +0 -109
- data/lib/tap/test/utils.rb +0 -231
@@ -0,0 +1,413 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'tap/schema'
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
class Schema
|
6
|
+
class << self
|
7
|
+
def parse(argv=ARGV)
|
8
|
+
Parser.new(argv).schema
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# A parser for workflow schema defined on the command line.
|
13
|
+
#
|
14
|
+
# == Syntax
|
15
|
+
#
|
16
|
+
# The command line syntax can be thought of as a series of ARGV arrays
|
17
|
+
# connected by breaks. The arrays define tasks (ie nodes) in a workflow
|
18
|
+
# while the breaks define joins. These are the available breaks:
|
19
|
+
#
|
20
|
+
# break meaning
|
21
|
+
# -- default delimiter, no join
|
22
|
+
# --: sequence join
|
23
|
+
# --[][] multi-join (sequence, fork, merge)
|
24
|
+
#
|
25
|
+
# As an example, this defines three tasks (a, b, c) and sequences the
|
26
|
+
# b and c tasks:
|
27
|
+
#
|
28
|
+
# schema = Parser.new("a -- b --: c").schema
|
29
|
+
# schema.tasks # => [["a"], ["b"], ["c"]]
|
30
|
+
# schema.joins # => [['join', [1],[2]]]
|
31
|
+
#
|
32
|
+
# In the example, the indicies of the tasks participating in the sequence
|
33
|
+
# are inferred as the last and next tasks in the schema, and obviously the
|
34
|
+
# location of the sequence break is significant. This isn't the case when
|
35
|
+
# the tasks in a join are explicitly specified. These both sequence a to
|
36
|
+
# b, and b to c.
|
37
|
+
#
|
38
|
+
# schema = Parser.new("a -- b -- c --0:1 --1:2").schema
|
39
|
+
# schema.tasks
|
40
|
+
# # => {
|
41
|
+
# # 0 => ["a"],
|
42
|
+
# # 1 => ["b"],
|
43
|
+
# # 2 => ["c"]
|
44
|
+
# # }
|
45
|
+
# schema.joins
|
46
|
+
# # => [
|
47
|
+
# # [[0],[1]],
|
48
|
+
# # [[1],[2]],
|
49
|
+
# # ]
|
50
|
+
#
|
51
|
+
# schema = Parser.new("a --1:2 --0:1 b -- c").schema
|
52
|
+
# schema.tasks
|
53
|
+
# # => {
|
54
|
+
# # 0 => ["a"],
|
55
|
+
# # 1 => ["b"],
|
56
|
+
# # 2 => ["c"]
|
57
|
+
# # }
|
58
|
+
# schema.joins
|
59
|
+
# # => [
|
60
|
+
# # [[1],[2]],
|
61
|
+
# # [[0],[1]],
|
62
|
+
# # ]
|
63
|
+
#
|
64
|
+
# ==== Multi-Join Syntax
|
65
|
+
#
|
66
|
+
# The multi-join syntax allows the specification of arbitrary joins.
|
67
|
+
# Starting with a few examples:
|
68
|
+
#
|
69
|
+
# example meaning
|
70
|
+
# --[][] last.sequence(next)
|
71
|
+
# --[1][2] 1.sequence(2)
|
72
|
+
# --[1][2,3] 1.fork(2,3)
|
73
|
+
# --[1,2][3] 3.merge(1,2)
|
74
|
+
#
|
75
|
+
# The meaning of the bracket breaks seems to be changing but note that
|
76
|
+
# the sequences, forks, and (unsynchronized) merges are all variations
|
77
|
+
# of a multi-way join. Internally the breaks are interpreted like this:
|
78
|
+
#
|
79
|
+
# join = Join.new
|
80
|
+
# join.join(inputs, outputs)
|
81
|
+
#
|
82
|
+
# To specify another class of join, or to specify join configurations,
|
83
|
+
# add a string in the format "configs.class" where the configs are the
|
84
|
+
# single-letter configuration flags and class is a lookup for the join
|
85
|
+
# class.
|
86
|
+
#
|
87
|
+
# example interpretation
|
88
|
+
# --:s Join.new(:splat => true)
|
89
|
+
# --1:2is Join.new(:iterate => true, :splat => true)
|
90
|
+
# --[][]q.sync Sync.new(:enq => true)
|
91
|
+
# --[][].sync Sync.new
|
92
|
+
#
|
93
|
+
# If you can stand the syntax, you can also specify a full argv after
|
94
|
+
# the bracket, just be sure to enclose the whole break in quotes.
|
95
|
+
#
|
96
|
+
# example interpretation
|
97
|
+
# "--1:2 join -i -s" Join.new(:iterate => true, :splat => true)
|
98
|
+
# "--[][] sync --enq" Sync.new(:enq => true)
|
99
|
+
#
|
100
|
+
# ==== Escapes and End Flags
|
101
|
+
#
|
102
|
+
# Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
|
103
|
+
# any number of arguments may be enclosed within the escape. After the
|
104
|
+
# end delimiter, breaks are active once again.
|
105
|
+
#
|
106
|
+
# schema = Parser.new("a -- b -- c").schema
|
107
|
+
# schema.tasks
|
108
|
+
# # => {
|
109
|
+
# # 0 => ["a"],
|
110
|
+
# # 1 => ["b"],
|
111
|
+
# # 2 => ["c"]
|
112
|
+
# # }
|
113
|
+
#
|
114
|
+
# schema = Parser.new("a -. -- b .- -- c").schema
|
115
|
+
# schema.tasks
|
116
|
+
# # => {
|
117
|
+
# # 0 => ["a", "--", "b"],
|
118
|
+
# # 1 => ["c"]
|
119
|
+
# # }
|
120
|
+
#
|
121
|
+
# Parsing continues until the end of argv, or a an end flag '---' is
|
122
|
+
# reached. The end flag may also be escaped.
|
123
|
+
#
|
124
|
+
# schema = Parser.new("a -- b --- c").schema
|
125
|
+
# schema.tasks
|
126
|
+
# # => {
|
127
|
+
# # 0 => ["a"],
|
128
|
+
# # 1 => ["b"]
|
129
|
+
# # }
|
130
|
+
#
|
131
|
+
class Parser
|
132
|
+
|
133
|
+
# A set of parsing routines used internally by Tap::Schema::Parser,
|
134
|
+
# modularized for ease of testing, and potential re-use. These methods
|
135
|
+
# require that <tt>current_index</tt> and <tt>previous_index</tt> be
|
136
|
+
# implemented in the including class.
|
137
|
+
module Utils
|
138
|
+
module_function
|
139
|
+
|
140
|
+
# The escape begin argument
|
141
|
+
ESCAPE_BEGIN = "-."
|
142
|
+
|
143
|
+
# The escape end argument
|
144
|
+
ESCAPE_END = ".-"
|
145
|
+
|
146
|
+
# The parser end flag
|
147
|
+
END_FLAG = "---"
|
148
|
+
|
149
|
+
# Matches any breaking arg. Examples:
|
150
|
+
#
|
151
|
+
# --
|
152
|
+
# --+
|
153
|
+
# --1:2
|
154
|
+
# --[1][2]
|
155
|
+
# --[1,2,3][4,5,6]is.join
|
156
|
+
#
|
157
|
+
# After the match:
|
158
|
+
#
|
159
|
+
# $1:: The string after the break
|
160
|
+
# (ex: '--' => '', '--:' => ':', '--[1,2][3,4]is.join' => '[1,2][3,4]is.join')
|
161
|
+
#
|
162
|
+
BREAK = /\A--(\z|[\d\:\[].*\z)/
|
163
|
+
|
164
|
+
# Matches a sequence break. Examples:
|
165
|
+
#
|
166
|
+
# :
|
167
|
+
# 1:
|
168
|
+
# :2
|
169
|
+
# 1:2:3
|
170
|
+
#
|
171
|
+
# After the match:
|
172
|
+
#
|
173
|
+
# $1:: The sequence string after the break.
|
174
|
+
# (ex: ':' => ':', '1:2' => '1:2', '1:' => '1:', ':2' => ':2')
|
175
|
+
# $2:: The modifier string.
|
176
|
+
# (ex: ':i' => 'i', '1:2is' => 'is')
|
177
|
+
#
|
178
|
+
SEQUENCE = /\A(\d*(?::\d*)+)(.*)\z/
|
179
|
+
|
180
|
+
# Matches a generic join break. Examples:
|
181
|
+
#
|
182
|
+
# "[1,2,3][4,5,6] join -i -s"
|
183
|
+
# [1,2,3][4,5,6]is.join
|
184
|
+
# [1,2][3,4]
|
185
|
+
# [1][2]
|
186
|
+
#
|
187
|
+
# After the match:
|
188
|
+
#
|
189
|
+
# $1:: The inputs string.
|
190
|
+
# (ex: '[1,2,3][4,5,6]' => '1,2,3')
|
191
|
+
# $2:: The outputs string.
|
192
|
+
# (ex: '[1,2,3][4,5,6]' => '4,5,6')
|
193
|
+
# $3:: The modifier string.
|
194
|
+
# (ex: '[][]is' => 'is')
|
195
|
+
#
|
196
|
+
JOIN = /\A\[([\d,]*)\]\[([\d,]*)\](.*)\z/
|
197
|
+
|
198
|
+
# Matches a join modifier. After the match:
|
199
|
+
#
|
200
|
+
# $1:: The modifier flag string.
|
201
|
+
# (ex: 'is.sync' => 'is')
|
202
|
+
# $2:: The class string.
|
203
|
+
# (ex: 'is.sync' => 'sync')
|
204
|
+
#
|
205
|
+
JOIN_MODIFIER = /\A([A-z]*)(?:\.(.*))?\z/
|
206
|
+
|
207
|
+
# Parses an indicies str along commas, and collects the indicies
|
208
|
+
# as integers. Ex:
|
209
|
+
#
|
210
|
+
# parse_indicies('') # => []
|
211
|
+
# parse_indicies('1') # => [1]
|
212
|
+
# parse_indicies('1,2,3') # => [1,2,3]
|
213
|
+
#
|
214
|
+
def parse_indicies(str, regexp=/,+/)
|
215
|
+
indicies = []
|
216
|
+
str.split(regexp).each do |n|
|
217
|
+
indicies << n.to_i unless n.empty?
|
218
|
+
end
|
219
|
+
indicies
|
220
|
+
end
|
221
|
+
|
222
|
+
# Parses the match of a SEQUENCE regexp an array of [input_indicies,
|
223
|
+
# output_indicies, metadata] arrays. The inputs corresponds to $1 and
|
224
|
+
# $2 for the match. The previous and current index are assumed if $1
|
225
|
+
# starts and/or ends with a semi-colon.
|
226
|
+
#
|
227
|
+
# parse_sequence("1:2:3", '')
|
228
|
+
# # => [
|
229
|
+
# # [[1], [2]],
|
230
|
+
# # [[2], [3]],
|
231
|
+
# # ]
|
232
|
+
#
|
233
|
+
# parse_sequence(":1:2:", 'is')
|
234
|
+
# # => [
|
235
|
+
# # [[:previous_index], [1], ['join', '-i', '-s']],
|
236
|
+
# # [[1], [2], ['join', '-i', '-s']]],
|
237
|
+
# # [[2], [:current_index], ['join', '-i', '-s']],
|
238
|
+
# # ]
|
239
|
+
#
|
240
|
+
def parse_sequence(one, two)
|
241
|
+
indicies = parse_indicies(one, /:+/)
|
242
|
+
indicies.unshift previous_index if one[0] == ?:
|
243
|
+
indicies << current_index if one[-1] == ?:
|
244
|
+
|
245
|
+
sequences = []
|
246
|
+
while indicies.length > 1
|
247
|
+
sequences << [[indicies.shift], [indicies[0]]]
|
248
|
+
end
|
249
|
+
|
250
|
+
if argv = parse_join_modifier(two)
|
251
|
+
sequences.each do |sequence|
|
252
|
+
sequence << argv
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
sequences
|
257
|
+
end
|
258
|
+
|
259
|
+
# Parses the match of a JOIN regexp into a [input_indicies,
|
260
|
+
# output_indicies, metadata] array. The inputs corresponds to $1, $2,
|
261
|
+
# and $3 for a match to a JOIN regexp. A join type of 'join' is
|
262
|
+
# assumed unless otherwise specified.
|
263
|
+
#
|
264
|
+
# parse_join("1", "2,3", "") # => [[1], [2,3]]
|
265
|
+
# parse_join("", "", "is.type") # => [[], [], ['type', '-i', '-s']]
|
266
|
+
# parse_join("", "", "type -i -s") # => [[], [], ['type', '-i', '-s']]
|
267
|
+
#
|
268
|
+
def parse_join(one, two, three)
|
269
|
+
join = [parse_indicies(one), parse_indicies(two)]
|
270
|
+
|
271
|
+
if argv = parse_join_modifier(three)
|
272
|
+
join << argv
|
273
|
+
end
|
274
|
+
|
275
|
+
join
|
276
|
+
end
|
277
|
+
|
278
|
+
# Parses a join modifier string into an argv.
|
279
|
+
def parse_join_modifier(modifier)
|
280
|
+
case modifier
|
281
|
+
when ""
|
282
|
+
nil
|
283
|
+
when JOIN_MODIFIER
|
284
|
+
argv = [$2 == nil || $2.empty? ? 'join' : $2]
|
285
|
+
$1.split("").each {|char| argv << "-#{char}"}
|
286
|
+
argv
|
287
|
+
else
|
288
|
+
Shellwords.shellwords(modifier)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
include Utils
|
294
|
+
|
295
|
+
# The schema into which tasks are being parsed
|
296
|
+
attr_reader :schema
|
297
|
+
|
298
|
+
def initialize(argv=[])
|
299
|
+
parse(argv)
|
300
|
+
end
|
301
|
+
|
302
|
+
# Iterates through the argv splitting out task and join definitions.
|
303
|
+
# Parse is non-destructive to argv. If a string argv is provided, parse
|
304
|
+
# splits it into an array using Shellwords; if a hash argv is provided,
|
305
|
+
# parse converts it to an array using Parser::Utils#parse_argh.
|
306
|
+
def parse(argv)
|
307
|
+
parse!(argv.kind_of?(String) ? argv : argv.dup)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Same as parse, but removes parsed args from argv.
|
311
|
+
def parse!(argv)
|
312
|
+
@current_index = 0
|
313
|
+
@schema = Schema.new
|
314
|
+
|
315
|
+
# prevent the addition of an empty task to schema
|
316
|
+
return if argv.empty?
|
317
|
+
|
318
|
+
argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
|
319
|
+
argv.unshift('--')
|
320
|
+
|
321
|
+
escape = false
|
322
|
+
current_task = nil
|
323
|
+
while !argv.empty?
|
324
|
+
arg = argv.shift
|
325
|
+
|
326
|
+
# if escaping, add escaped arguments
|
327
|
+
# until an escape-end argument
|
328
|
+
if escape
|
329
|
+
if arg == ESCAPE_END
|
330
|
+
escape = false
|
331
|
+
else
|
332
|
+
(current_task ||= task(current_index)) << arg
|
333
|
+
end
|
334
|
+
|
335
|
+
next
|
336
|
+
end
|
337
|
+
|
338
|
+
case arg
|
339
|
+
when ESCAPE_BEGIN
|
340
|
+
# begin escaping if indicated
|
341
|
+
escape = true
|
342
|
+
|
343
|
+
when END_FLAG
|
344
|
+
# break on an end-flag
|
345
|
+
break
|
346
|
+
|
347
|
+
when BREAK
|
348
|
+
# a breaking argument was reached:
|
349
|
+
# unless the current argv is empty,
|
350
|
+
# append and start a new definition
|
351
|
+
if current_task && !current_task.empty?
|
352
|
+
self.current_index += 1
|
353
|
+
current_task = nil
|
354
|
+
end
|
355
|
+
|
356
|
+
# parse the break string for any
|
357
|
+
# schema modifications
|
358
|
+
parse_break($1)
|
359
|
+
|
360
|
+
else
|
361
|
+
# add all other non-breaking args to
|
362
|
+
# the current argv; this includes
|
363
|
+
# both inputs and configurations
|
364
|
+
(current_task ||= task(current_index)) << arg
|
365
|
+
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
schema
|
370
|
+
end
|
371
|
+
|
372
|
+
protected
|
373
|
+
|
374
|
+
# The index of the task currently being parsed.
|
375
|
+
attr_accessor :current_index # :nodoc:
|
376
|
+
|
377
|
+
# helper to initialize a task at the specified index
|
378
|
+
def task(index) # :nodoc:
|
379
|
+
schema.tasks[index] ||= []
|
380
|
+
end
|
381
|
+
|
382
|
+
# returns current_index-1, or raises an error if current_index < 1.
|
383
|
+
def previous_index # :nodoc:
|
384
|
+
current_index - 1
|
385
|
+
end
|
386
|
+
|
387
|
+
# determines the type of break and modifies self appropriately
|
388
|
+
def parse_break(arg) # :nodoc:
|
389
|
+
case arg
|
390
|
+
when ""
|
391
|
+
unless schema.queue.include?(current_index)
|
392
|
+
schema.queue << current_index
|
393
|
+
end
|
394
|
+
when SEQUENCE
|
395
|
+
parse_sequence($1, $2).each {|join| set_join(join) }
|
396
|
+
when JOIN
|
397
|
+
set_join(parse_join($1, $2, $3))
|
398
|
+
else
|
399
|
+
raise ArgumentError, "invalid break argument: #{arg}"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# constructs the specified join and removes the targets of the
|
404
|
+
# join from the queue
|
405
|
+
def set_join(join) # :nodoc:
|
406
|
+
join[1].each do |output|
|
407
|
+
schema.queue.delete(output)
|
408
|
+
end
|
409
|
+
schema.joins << join
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Tap
|
2
|
+
class Schema
|
3
|
+
module Utils
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def instantiate(data, app)
|
7
|
+
case data
|
8
|
+
when Hash then data['class'].instantiate(symbolize(data), app)
|
9
|
+
when Array then data.shift.parse(data, app)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolved?(data)
|
14
|
+
case data
|
15
|
+
when Hash then data['class'].respond_to?(:instantiate)
|
16
|
+
when Array then data[0].respond_to?(:parse)
|
17
|
+
else false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve(data)
|
22
|
+
return data if resolved?(data)
|
23
|
+
|
24
|
+
case data
|
25
|
+
when Hash
|
26
|
+
data['class'] = yield(data['id'], data)
|
27
|
+
when Array
|
28
|
+
data[0] = yield(data[0], data)
|
29
|
+
end
|
30
|
+
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
# Symbolizes the keys of hash. Returns non-hash values directly and
|
35
|
+
# raises an error in the event of a symbolize conflict.
|
36
|
+
def symbolize(hash)
|
37
|
+
return hash unless hash.kind_of?(Hash)
|
38
|
+
|
39
|
+
result = {}
|
40
|
+
hash.each_pair do |key, value|
|
41
|
+
key = key.to_sym || key
|
42
|
+
|
43
|
+
if result.has_key?(key)
|
44
|
+
raise "symbolize conflict: #{hash.inspect} (#{key.inspect})"
|
45
|
+
end
|
46
|
+
|
47
|
+
result[key] = value
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the values for hash sorted by key. Returns non-hash objects
|
53
|
+
# directly.
|
54
|
+
def dehashify(obj)
|
55
|
+
case obj
|
56
|
+
when nil then []
|
57
|
+
when Hash then obj.keys.sort.collect {|key| obj[key] }
|
58
|
+
else obj
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns obj as a hash, using the index of each element as the
|
63
|
+
# key for the element. The object must respond to each. Returns
|
64
|
+
# hashes directly.
|
65
|
+
def hashify(obj)
|
66
|
+
case obj
|
67
|
+
when nil then {}
|
68
|
+
when Hash then obj
|
69
|
+
else
|
70
|
+
index = 0
|
71
|
+
hash = {}
|
72
|
+
obj.each do |entry|
|
73
|
+
hash[index] = entry
|
74
|
+
index += 1
|
75
|
+
end
|
76
|
+
hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|