madderlib 0.1.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.
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