madderlib 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG +3 -0
  2. data/LICENSE +22 -0
  3. data/README.rdoc +173 -0
  4. data/Rakefile +134 -0
  5. data/lib/madderlib.rb +27 -0
  6. data/lib/madderlib/builder.rb +659 -0
  7. data/lib/madderlib/conditional/allowed.rb +144 -0
  8. data/lib/madderlib/conditional/helper.rb +135 -0
  9. data/lib/madderlib/conditional/likely.rb +162 -0
  10. data/lib/madderlib/conditional/recur.rb +103 -0
  11. data/lib/madderlib/conditional/registry.rb +56 -0
  12. data/lib/madderlib/conditional/repeat.rb +130 -0
  13. data/lib/madderlib/context.rb +140 -0
  14. data/lib/madderlib/core.rb +217 -0
  15. data/lib/madderlib/extensions.rb +40 -0
  16. data/lib/madderlib/instruction.rb +171 -0
  17. data/lib/madderlib/phrase.rb +284 -0
  18. data/lib/madderlib/sequencer.rb +337 -0
  19. data/madderlib.gemspec +72 -0
  20. data/spec/benchmark_spec.rb +69 -0
  21. data/spec/builder_spec.rb +321 -0
  22. data/spec/builder_to_other_spec.rb +47 -0
  23. data/spec/builder_to_sequencer_spec.rb +388 -0
  24. data/spec/conditional_allowed_spec.rb +130 -0
  25. data/spec/conditional_helper_spec.rb +131 -0
  26. data/spec/conditional_likely_spec.rb +138 -0
  27. data/spec/conditional_recur_spec.rb +102 -0
  28. data/spec/conditional_registry_spec.rb +94 -0
  29. data/spec/conditional_repeat_spec.rb +88 -0
  30. data/spec/doc_spec.rb +550 -0
  31. data/spec/error_spec.rb +33 -0
  32. data/spec/examples_spec.rb +151 -0
  33. data/spec/extensions_spec.rb +47 -0
  34. data/spec/grammar_spec.rb +101 -0
  35. data/spec/instruction_spec.rb +133 -0
  36. data/spec/kernel_spec.rb +58 -0
  37. data/spec/phrase_spec.rb +7 -0
  38. data/spec/sequencer_spec.rb +317 -0
  39. data/spec/spec_helper.rb +54 -0
  40. metadata +98 -0
@@ -0,0 +1,321 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Builder, "building" do
6
+ before(:each) do
7
+ @builder = MadderLib::Builder.new
8
+ end
9
+
10
+
11
+
12
+ it "doesn't require an id" do
13
+ @builder.id.should be_nil
14
+
15
+ @builder = MadderLib::Builder.new :id
16
+ @builder.id.should equal(:id)
17
+ end
18
+
19
+ it "transfers a phrase into a Sequence" do
20
+ @builder.should have(0).phrases
21
+ @builder.phrase.should be_nil
22
+
23
+ phrase = @builder.then
24
+ phrase.should_not be_nil
25
+ phrase.id.should be_nil
26
+
27
+ @builder.should have(1).phrases
28
+ @builder.phrases.last.should equal(phrase)
29
+ @builder.phrase.should equal(phrase)
30
+
31
+ # one more
32
+ phrase = @builder.and_then
33
+
34
+ @builder.should have(2).phrases
35
+ @builder.phrases.last.should equal(phrase)
36
+ @builder.phrase.should equal(phrase)
37
+
38
+ # one more, with an id
39
+ phrase = @builder.also(:id)
40
+ phrase.id.should equal(:id)
41
+
42
+ @builder.should have(3).phrases
43
+ @builder.phrases.last.should equal(phrase)
44
+ @builder.phrase.should equal(phrase)
45
+ end
46
+
47
+
48
+
49
+ it "has id support for some phrase construction methods, not for others" do
50
+ [
51
+ :a, :an,
52
+ :then, :and_then, :also,
53
+ :first, :last, :lastly,
54
+ :anytime,
55
+ ].each do |method|
56
+ # the method takes just an id
57
+ phrase = @builder.__send__ method, method
58
+ phrase.id.should equal(method)
59
+ end
60
+
61
+ [
62
+ :before, :after,
63
+ ].each do |method|
64
+ # the method takes a reference, then the id
65
+ phrase = @builder.__send__ method, :ref, method
66
+ phrase.id.should equal(method)
67
+ end
68
+ end
69
+
70
+ it "#it is an alias for #phrase" do
71
+ @builder.it.should equal(@builder.phrase)
72
+ @builder.it.should be_nil
73
+
74
+ @builder.then
75
+ @builder.should have(1).phrases
76
+ @builder.it.should equal(@builder.phrase)
77
+ end
78
+
79
+ it "#a & #and are aliases for #and_then, requiring an id" do
80
+ @builder.should have(0).phrases
81
+
82
+ phrase = @builder.phrase
83
+ @builder.a(:new).should_not equal(phrase)
84
+ @builder.should have(1).phrases
85
+
86
+ phrase = @builder.phrase
87
+
88
+ @builder.an(:other).should_not equal(phrase)
89
+ @builder.should have(2).phrases
90
+ end
91
+
92
+ it "will not permit duplicate phrase ids" do
93
+ # this will apply to all id-capable methods
94
+ @builder.a(:first)
95
+ @builder.a(:second)
96
+ lambda { @builder.a(:first) }.should raise_error(MadderLib::Error)
97
+ lambda { @builder.a(:second) }.should raise_error(MadderLib::Error)
98
+ end
99
+
100
+
101
+
102
+ it "has a 'say' shortcut" do
103
+ @builder.should have(0).phrases
104
+
105
+ phrase = @builder.say('hello')
106
+ phrase.should_not be_nil
107
+ phrase.id.should be_nil
108
+
109
+ @builder.should have(1).phrases
110
+ @builder.phrases.last.should equal(phrase)
111
+
112
+ # builds a new phrase
113
+ # even with the same words, it's different
114
+ phrase = @builder.say('hello')
115
+
116
+ @builder.should have(2).phrases
117
+
118
+ # we do not try to pull an id out of the say
119
+ # use and_then, or a similar explicit phrasing method
120
+ phrase = @builder.say(:it, 'goodbye')
121
+ phrase.id.should be_nil
122
+
123
+ @builder.should have(3).phrases
124
+ end
125
+
126
+
127
+
128
+ # best to test this once we have 'say' in the mix
129
+ #
130
+ it "'extend' operates in the context of the builder" do
131
+ words = %w{ hello goodbye }
132
+
133
+ @builder.extend do
134
+ say words.shift
135
+ and_then.say words.shift
136
+ end
137
+
138
+ @builder.should have(2).phrases
139
+ end
140
+
141
+ it "automatic 'extend' during construction" do
142
+ words = %w{ hello goodbye }
143
+
144
+ @builder = MadderLib::Builder.new do
145
+ say 'hello'
146
+ also.say 'goodbye'
147
+ end
148
+
149
+ @builder.should have(2).phrases
150
+
151
+ # with an id
152
+ @builder = MadderLib::Builder.new(:id) do
153
+ say 'hello'
154
+ # just testing syntactic sugar
155
+ an(:other).says 'goodbye'
156
+ end
157
+
158
+ @builder.id.should equal(:id)
159
+ @builder.should have(2).phrases
160
+ end
161
+
162
+
163
+
164
+ it "has an 'or' shortcut" do
165
+ @builder.say 'hello'
166
+ @builder.should have(1).phrases
167
+
168
+ @builder.or.say 'aloha'
169
+ @builder.should have(1).phrases
170
+
171
+ @builder.append do
172
+ alternately.say 'konnichiwa'
173
+ end
174
+ @builder.should have(1).phrases
175
+ end
176
+
177
+ it "the 'or' shortcut requires a pre-existing phrase" do
178
+ lambda { @builder.or }.should raise_error(MadderLib::Error)
179
+ end
180
+
181
+
182
+
183
+ it "categorizes orderings" do
184
+ @builder.say 'middle'
185
+ phrase = @builder.first.say 'beginning'
186
+
187
+ @builder.should have(2).phrases
188
+ @builder.should have(1).orderings
189
+ @builder.should have(0).dependencies
190
+
191
+ model = @builder.orderings.last
192
+ model.type.should equal(:first)
193
+ model.phrase.should equal(phrase)
194
+
195
+ phrase = @builder.last.say 'end'
196
+
197
+ @builder.should have(3).phrases
198
+ @builder.should have(2).orderings
199
+
200
+ model = @builder.orderings.last
201
+ model.type.should equal(:last)
202
+ model.phrase.should equal(phrase)
203
+
204
+ phrase = @builder.anytime.say 'illustration'
205
+
206
+ @builder.should have(4).phrases
207
+ @builder.should have(3).orderings
208
+
209
+ model = @builder.orderings.last
210
+ model.type.should equal(:anytime)
211
+ model.phrase.should equal(phrase)
212
+
213
+ phrase = @builder.anytime.say 'table'
214
+ @builder.orderings.last.phrase.should equal(phrase)
215
+
216
+ phrase = @builder.last.say 'appendix'
217
+ @builder.orderings.last.phrase.should equal(phrase)
218
+
219
+ phrase = @builder.first.say 'contents'
220
+ @builder.orderings.last.phrase.should equal(phrase)
221
+ end
222
+
223
+ it "has dependencies which require reference ids" do
224
+ lambda { @builder.before.say 'no id' }.should raise_error
225
+ lambda { @builder.after.say 'no id' }.should raise_error
226
+ end
227
+
228
+ it "categorizes dependencies" do
229
+ phrase = @builder.before(:use).say 'open'
230
+
231
+ @builder.should have(1).phrases
232
+ @builder.should have(0).orderings
233
+ @builder.should have(1).dependencies
234
+
235
+ dep = @builder.dependencies.last
236
+ dep.ref.should equal(:use)
237
+ dep.phrase.should equal(phrase)
238
+ dep.type.should equal(:before)
239
+
240
+ phrase = @builder.after(:use).say 'close'
241
+
242
+ @builder.should have(2).phrases
243
+ @builder.should have(2).dependencies
244
+
245
+ dep = @builder.dependencies.last
246
+ dep.ref.should equal(:use)
247
+ dep.phrase.should equal(phrase)
248
+ dep.type.should equal(:after)
249
+
250
+ phrase = @builder.before(:use).say 'destroy'
251
+ @builder.dependencies.last.phrase.should equal(phrase)
252
+
253
+ phrase = @builder.before(:use).say 'init'
254
+ @builder.dependencies.last.phrase.should equal(phrase)
255
+ end
256
+
257
+
258
+
259
+ it "can be cloned and extended" do
260
+ builder = madderlib :clone_original do
261
+ an(:early).says 'early'
262
+ a(:late).says 'late'
263
+ end
264
+ builder.id.should equal(:clone_original)
265
+
266
+ # clone
267
+ extended = builder.clone(:clone_extended).extend do
268
+ before(:early).say "it's"
269
+ after(:late).say 'bird'
270
+ end
271
+ extended[:meta] = :extended
272
+ metas = nil
273
+ block = lambda {|context| metas << context.builder[:meta] }
274
+ extended.setup &block
275
+ extended.teardown &block
276
+
277
+ # modify a shared Phrase
278
+ shared = builder.phrases.find {|phrase| phrase.id == :late }
279
+ shared.instructions.first.words << 'later'
280
+
281
+ # new id, in the registry
282
+ extended.id.should equal(:clone_extended)
283
+ extended.should equal(madderlib_grammar[:clone_extended])
284
+
285
+ # setup, teardown, meta, and extended content
286
+ metas = []
287
+ extended.sentence.should eql("it's early late later bird")
288
+ metas.should eql([:extended, :extended])
289
+
290
+ # original is not impacted
291
+ # except for the shared Phrase
292
+ metas = []
293
+ builder.sentence.should eql('early late later')
294
+ builder[:meta].should be_nil
295
+ metas.should have(0).items
296
+ end
297
+
298
+ it "adds the clone to the active Grammar if its original is there" do
299
+ grammar = MadderLib::Grammar.get_instance
300
+
301
+ original = MadderLib::Builder.new
302
+ grammar.builders.include?(original).should_not be_true
303
+
304
+ cloned = original.clone
305
+
306
+ grammar.add original
307
+ added = original.clone
308
+ grammar.builders.include?(added).should be_true
309
+ end
310
+
311
+
312
+
313
+ it "holds metadata for you" do
314
+ builder = madderlib do
315
+ meta[:cow] = :cow
316
+ builder.meta[:dog] = :dog
317
+
318
+ [:cow, :dog].each {|item| builder[item].should equal(item) }
319
+ end
320
+
321
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Builder, "has component parts" do
6
+ WORDS = %w{ one two three }
7
+
8
+
9
+
10
+ it "is iterable" do
11
+ words = []
12
+
13
+ (madderlib do
14
+ WORDS.each {|word| say word }
15
+ end).each do |word|
16
+ words << word
17
+ end
18
+
19
+ words.should eql(WORDS)
20
+ end
21
+
22
+ it "converts to an Array" do
23
+ array = (madderlib do
24
+ WORDS.each {|word| say word }
25
+ end).to_a
26
+
27
+ array.should eql(WORDS)
28
+ end
29
+
30
+ it "converts to a String" do
31
+ s = (madderlib do
32
+ WORDS.each {|word| say word }
33
+ end).to_s
34
+
35
+ # default separator
36
+ s.should eql(WORDS.join(' '))
37
+ end
38
+
39
+ it "converts to a String, with separator" do
40
+ s = (madderlib do
41
+ WORDS.each {|word| say word }
42
+ end).to_s("\t")
43
+
44
+ # explicit separator
45
+ s.should eql(WORDS.join("\t"))
46
+ end
47
+ end
@@ -0,0 +1,388 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Builder, "to Sequencer" do
6
+
7
+ it "turns an empty builder into an empty sequencer" do
8
+ sequencer = MadderLib::Builder.new.to_sequencer
9
+
10
+ sequencer.should have(0).steps
11
+ sequencer.should have(0).anytimes
12
+ sequencer.should have(0).befores
13
+ sequencer.should have(0).afters
14
+ end
15
+
16
+ it "handles a single phrase" do
17
+ builder = MadderLib::Builder.new()
18
+ phrase = builder.say('puh-TAY-to').or.say('puh-TAH-to')
19
+ sequencer = builder.to_sequencer
20
+
21
+ sequencer.should have(1).steps
22
+ sequencer.should have(0).anytimes
23
+ sequencer.should have(0).befores
24
+ sequencer.should have(0).afters
25
+
26
+ sequencer.steps.last.should equal(phrase)
27
+ end
28
+
29
+ it "requires valid ids for befores and afters" do
30
+ builder = madderlib do
31
+ before(:missing).say('uh')
32
+ end
33
+ lambda { builder.to_sequencer }.should raise_error(MadderLib::Error)
34
+
35
+ builder = madderlib do
36
+ after(:missing).say('whoa')
37
+ end
38
+ lambda { builder.to_sequencer }.should raise_error(MadderLib::Error)
39
+ end
40
+
41
+ it "reminder... Builder#say doesn't derive a Phrase id" do
42
+ phrase = nil
43
+ builder = madderlib do
44
+ phrase = say(:it, 'words')
45
+ before(:it).say('something')
46
+ end
47
+
48
+ phrase.id.should be_nil
49
+ lambda { builder.to_sequencer }.should raise_error(MadderLib::Error)
50
+ end
51
+
52
+
53
+
54
+ it "properly sequences firsts" do
55
+ builder = madderlib :sequence_befores do
56
+ says('loud')
57
+ first.say('too')
58
+ first.say('yer')
59
+ end
60
+
61
+ sequencer = builder.to_sequencer
62
+ sequencer.should have(3).steps
63
+
64
+ ids = []
65
+ sequencer.each_phrase do |phrase|
66
+ phrase.should have(1).instructions
67
+ words = phrase.instructions.last.words
68
+ words.should have(1).items
69
+
70
+ ids << words.last
71
+ end
72
+ ids.should eql(%w{ yer too loud })
73
+ end
74
+
75
+ it "properly sequences lasts" do
76
+ builder = madderlib do
77
+ last.say('too')
78
+ lastly.say('big')
79
+ say('feet')
80
+ end
81
+
82
+ sequencer = builder.to_sequencer
83
+ sequencer.should have(3).steps
84
+
85
+ ids = []
86
+ sequencer.each_phrase do |phrase|
87
+ phrase.should have(1).instructions
88
+ words = phrase.instructions.last.words
89
+ words.should have(1).items
90
+
91
+ ids << words.last
92
+ end
93
+ ids.should eql(%w{ feet too big })
94
+ end
95
+
96
+
97
+
98
+ it "before and after require referenceable ids" do
99
+ # doesn't complain on build
100
+ builder = madderlib do
101
+ before(:say).say 'before'
102
+ say 'saying'
103
+ end
104
+ # but during validation, etc.
105
+ lambda { builder.validate }.should raise_error MadderLib::Error
106
+
107
+ builder = madderlib do
108
+ after(:say).say 'after'
109
+ say 'saying'
110
+ end
111
+ lambda { builder.validate }.should raise_error MadderLib::Error
112
+ end
113
+
114
+ it "moves befores and afters into their own little worlds" do
115
+ builder = madderlib do
116
+ after(:say, :after).say 'after'
117
+ before(:say, :before).say 'before'
118
+ a(:say).says 'says'
119
+ end
120
+
121
+ sequencer = builder.to_sequencer
122
+ sequencer.should have(1).steps
123
+
124
+ # before
125
+ sequencer.should have(1).befores
126
+ dep = sequencer.befores[:say]
127
+ dep.should have(1).items
128
+ dep.last.instructions.last.words.last.should eql('before')
129
+
130
+ # after
131
+ sequencer.should have(1).afters
132
+ dep = sequencer.afters[:say]
133
+ dep.should have(1).items
134
+ dep.last.instructions.last.words.last.should eql('after')
135
+
136
+ # some more, to prove ordering
137
+ builder.append do
138
+ after(:say).say 'end'
139
+ before(:say).say 'begin'
140
+ end
141
+
142
+ sequencer = builder.to_sequencer
143
+
144
+ # before
145
+ words = []
146
+ sequencer.befores[:say].each do |before|
147
+ words << before.instructions.last.words.last
148
+ end
149
+ words.should eql(['begin', 'before'])
150
+
151
+ # after
152
+ words = []
153
+ sequencer.afters[:say].each do |after|
154
+ words << after.instructions.last.words.last
155
+ end
156
+ words.should eql(['after', 'end'])
157
+ end
158
+
159
+
160
+
161
+ it "anytime requires referenceable ids" do
162
+ # doesn't complain on build
163
+ builder = madderlib do
164
+ anytime.say('dipsy').before(:say)
165
+ say 'doodle'
166
+ end
167
+ # but during validation, etc.
168
+ lambda { builder.validate }.should raise_error MadderLib::Error
169
+
170
+ builder = madderlib do
171
+ anytime.say('doodle').after(:say)
172
+ say 'dipsy'
173
+ end
174
+ lambda { builder.validate }.should raise_error MadderLib::Error
175
+
176
+ builder = madderlib do
177
+ anytime.say('zzz').between(:night, :day)
178
+ say 'ni-night'
179
+ end
180
+ lambda { builder.validate }.should raise_error MadderLib::Error
181
+ end
182
+
183
+ it "anytimes go into their own little world" do
184
+ builder = madderlib :sequence_befores do
185
+ anytime(:b).before(:say).say('before')
186
+ anytime(:a).after(:say).say('after')
187
+ anytime(:ab).say('somewhere').after(:b).before(:a)
188
+ anytime(:t).between(:b, :a).say('tween')
189
+ a(:say).says 'hello'
190
+ end
191
+
192
+ sequencer = builder.to_sequencer
193
+ sequencer.should have(1).steps
194
+
195
+ sequencer.should have(4).anytimes
196
+
197
+ ids = []
198
+ sequencer.anytimes.each do |anytime|
199
+ id = anytime.id
200
+ ids << id
201
+ anytime.before.should_not be_nil unless :a == id
202
+ anytime.after.should_not be_nil unless :b == id
203
+ end
204
+
205
+ ids.should eql([:b, :a, :ab, :t])
206
+ end
207
+
208
+
209
+
210
+ it "blends everything together perfectly" do
211
+ builder = madderlib :sequence_befores do
212
+ last(:late).say('4')
213
+ first(:early).say('2')
214
+ last.say('5')
215
+ first.say('1')
216
+
217
+ before(:late).say('3.9')
218
+ after(:early).say('2.1')
219
+ before(:early).say('1.9')
220
+ after(:late).say('4.1')
221
+
222
+ anytime.between(:early, :late).say('imaginary')
223
+ anytime.say('random')
224
+
225
+ says('3')
226
+ end
227
+
228
+ sequencer = builder.to_sequencer
229
+ sequencer.should have(5).steps
230
+
231
+ marks = []
232
+ sequencer.steps.each do |phrase|
233
+ phrase.should have(1).instructions
234
+ words = phrase.instructions.last.words
235
+ words.should have(1).items
236
+
237
+ marks << words.last
238
+ end
239
+ marks.should eql(%w{ 1 2 3 4 5 })
240
+
241
+ sequencer.befores[:early].last.instructions.last.words.last.should eql('1.9')
242
+ sequencer.afters[:early].last.instructions.last.words.last.should eql('2.1')
243
+ sequencer.befores[:late].last.instructions.last.words.last.should eql('3.9')
244
+ sequencer.afters[:late].last.instructions.last.words.last.should eql('4.1')
245
+
246
+ marks = []
247
+ sequencer.anytimes.each do |phrase|
248
+ marks << phrase.instructions.last.words.last
249
+ end
250
+ marks.should eql(%w{ imaginary random })
251
+ end
252
+
253
+
254
+
255
+ it "supports setup and teardown blocks" do
256
+ holder = []
257
+
258
+ sequencer = (madderlib do
259
+ # takes context, uses data, get and set local scope
260
+ # multiple are handled sequentially
261
+ setup {|context| holder << :setup }
262
+ setup {|context| context.data[:word] = holder.first }
263
+ setup {|context| holder << :a_s }
264
+ setup(:first) {|context| holder << :b_s }
265
+
266
+ # takes context
267
+ say {|context| context.data[:word] }
268
+
269
+ # doesn't need context, set local scope
270
+ # multiple are handled sequentially
271
+ teardown { holder << :teardown }
272
+ teardown(:first) { holder << :b_t }
273
+ teardown { holder << :a_t }
274
+ end).to_sequencer
275
+
276
+ # due to execution sequence...
277
+ sequencer.words.should eql(%w{ b_s })
278
+
279
+ holder.should eql([:b_s, :setup, :a_s, :b_t, :teardown, :a_t])
280
+ end
281
+
282
+
283
+
284
+ it "can collect the executed context" do
285
+ builder = madderlib :outer do
286
+ say madderlib(:inner_1) { say 'inner' }
287
+ say 'plain'
288
+ # tried a do .. end block here, wasn't seen
289
+ # using { .. } does work
290
+ say madderlib(:inner_2) {
291
+ say madderlib(:deep_1) { say 'deep' }
292
+ say madderlib(:deep_2) { say 'deeper' }
293
+ }
294
+ end
295
+
296
+ # capture the context
297
+ # setup or teardown, not important
298
+ context, count = nil, 0
299
+ words = builder.words do |ctx|
300
+ context = ctx
301
+ count = ctx.spoken.size
302
+ end
303
+ words.should eql(%w{ inner plain deep deeper })
304
+
305
+ # it'll be called before the context is used
306
+ count.should eql(0)
307
+
308
+ # and here's what you get back
309
+ context.should_not be_nil
310
+ context.spoken.should have(3).phrases
311
+ context.silent.should have(0).phrases
312
+ context.instructions.should have(3).instructions
313
+
314
+ # the :flat approach (default)
315
+ context.contexts.should have(4).contexts
316
+
317
+ # just the sub-contexts, not the outer builder
318
+ ids = []
319
+ context.contexts.each {|ctx| ids << ctx.builder.id }
320
+
321
+ ids.should have(4).ids
322
+ ids.should eql([ :inner_1, :inner_2, :deep_1, :deep_2 ])
323
+
324
+ # the :tree approach
325
+ # just the inner ones
326
+ context.contexts(:tree).should have(2).contexts
327
+
328
+ # hierarchical traversal
329
+ # this traversal provides a full tree
330
+ # including he the outer builder
331
+ ids = []
332
+ traverse = lambda do |ctx|
333
+ ids << ctx.builder.id
334
+ ctx.contexts(:tree).each {|sub| traverse.call(sub) }
335
+ end
336
+ traverse.call(context)
337
+
338
+ ids.should have(5).ids
339
+ ids.should eql([:outer, :inner_1, :inner_2, :deep_1, :deep_2 ])
340
+ end
341
+
342
+ it "can inject data into the context" do
343
+ builder = madderlib :inject_data do
344
+ say '('
345
+ say {|context| context[:text] }
346
+ say ')'
347
+
348
+ # text is nil, so doesn't appear
349
+ builder.words.should eql(['(', ')'])
350
+
351
+ # round trip with simple value
352
+ # can't pull out the context with enumerator-based methods
353
+ # but you can still inject data
354
+ context = nil
355
+
356
+ words = builder.words(:text => 'words') {|ctx| context = ctx }
357
+ context[:text].should eql('words')
358
+ words.should eql(['(', context[:text], ')'])
359
+
360
+ s = ''
361
+ builder.each_word(:text => 'each_word') {|word| s << word }
362
+ s.should eql('(each_word)')
363
+
364
+ # proper handling of data / separator mixing
365
+ builder.sentence.should eql('( )')
366
+ builder.sentence(:text => 'sentence').should eql('( sentence )')
367
+ builder.sentence('', :text => 'sentence').should eql('(sentence)')
368
+ map = { :text => 'sentence' }
369
+ builder.sentence(map, '.').should eql('(.sentence.)')
370
+
371
+ # so, let's try an indirection through the sequence
372
+ # we're testing by omission
373
+ # we won't see a phrase if the text is nil
374
+ sequencer = builder.to_sequencer
375
+
376
+ sequencer.phrases.should have(2).phrases
377
+ sequencer.phrases(:text => 'phrase').should have(3).phrases
378
+
379
+ phrases = []
380
+ sequencer.each_phrase {|phrase| phrases << phrase }
381
+ phrases.should have(2).phrases
382
+
383
+ phrases = []
384
+ sequencer.each_phrase(:text => 'phrase') {|phrase| phrases << phrase }
385
+ phrases.should have(3).phrases
386
+ end
387
+
388
+ end