tap 0.12.4 → 0.17.0
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.
- 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
|