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,130 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Conditional::Allowed do
6
+
7
+ it "supports single assumption" do
8
+ one, two = nil, nil
9
+
10
+ builder = madderlib do
11
+ say(:one).assuming {|test| one }
12
+ say(:two).presuming {|test| two }
13
+ say(:three).if { false }
14
+ end
15
+
16
+ [nil, true].each do |t|
17
+ two = t
18
+ [false, true].each do |o|
19
+ one = o
20
+
21
+ words = []
22
+ words << 'one' if one
23
+ words << 'two' if two
24
+
25
+ builder.words.should eql(words)
26
+ end
27
+ end
28
+ end
29
+
30
+ it "supports multiple assumptions" do
31
+ one, two = nil, nil
32
+
33
+ builder = madderlib do
34
+ say(:something).if {|test| one }.assuming {|test| two }
35
+ end
36
+
37
+ [nil, true].each do |t|
38
+ two = t
39
+ [false, true].each do |o|
40
+ one = o
41
+
42
+ words = []
43
+ words << 'something' if one && two
44
+
45
+ builder.words.should eql(words)
46
+ end
47
+ end
48
+ end
49
+
50
+ it "supports id-based assumption" do
51
+ # will say first = yes
52
+ builder = madderlib do
53
+ say(:yes).assuming(:spoken)
54
+ first(:spoken).say(:said)
55
+ end
56
+
57
+ builder.words.should eql(%w{ said yes })
58
+
59
+ # will say last = no
60
+ builder = madderlib do
61
+ say(:yes).if(:spoken)
62
+ last(:spoken).say(:said)
63
+ end
64
+
65
+ builder.words.should eql(%w{ said })
66
+ end
67
+
68
+
69
+
70
+ it "supports single forbiddance" do
71
+ one, two = nil, nil
72
+
73
+ builder = madderlib do
74
+ say(:one).forbidding {|test| one }
75
+ say(:two).forbidding {|test| two }
76
+ say(:three).unless { true }
77
+ end
78
+
79
+ [nil, true].each do |t|
80
+ two = t
81
+ [false, true].each do |o|
82
+ one = o
83
+
84
+ words = []
85
+ words << 'one' unless one
86
+ words << 'two' unless two
87
+
88
+ builder.words.should eql(words)
89
+ end
90
+ end
91
+ end
92
+
93
+ it "supports multiple forbiddance" do
94
+ one, two = nil, nil
95
+
96
+ builder = madderlib do
97
+ say(:something).unless {|test| one }.forbidding {|test| two }
98
+ end
99
+
100
+ [nil, true].each do |t|
101
+ two = t
102
+ [false, true].each do |o|
103
+ one = o
104
+
105
+ words = []
106
+ words << 'something' unless one || two
107
+
108
+ builder.words.should eql(words)
109
+ end
110
+ end
111
+ end
112
+
113
+ it "supports id-based forbiddance" do
114
+ # will say first = no
115
+ builder = madderlib do
116
+ say(:yes).forbidding(:spoken)
117
+ first(:spoken).say(:said)
118
+ end
119
+
120
+ builder.words.should eql(%w{ said })
121
+
122
+ # will say last = yes
123
+ builder = madderlib do
124
+ say(:yes).unless(:spoken)
125
+ last(:spoken).say(:said)
126
+ end
127
+
128
+ builder.words.should eql(%w{ yes said })
129
+ end
130
+ end
@@ -0,0 +1,131 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Conditional::Helper do
6
+
7
+ it "a TestBlock fails with invalid arguments" do
8
+ # arg or block, not both
9
+ lambda {
10
+ MadderLib::Conditional::Helper::TestBlock.new(1) {|count| count < 1 }
11
+ }.should raise_error MadderLib::Error
12
+
13
+ # unsupported
14
+ lambda {
15
+ MadderLib::Conditional::Helper::TestBlock.new(:symbol)
16
+ }.should raise_error MadderLib::Error
17
+ end
18
+
19
+ it "a TestBlock with a block" do
20
+ tester = MadderLib::Conditional::Helper::TestBlock.new {|count| count < 1 }
21
+
22
+ built = tester.block
23
+
24
+ built.call(0).should be_true
25
+
26
+ tester.invoke(0, :ignore).should be_true
27
+ tester.invoke(1, :ignore).should_not be_true
28
+ end
29
+
30
+ it "a TestBlock with a fixed limit" do
31
+ tester = MadderLib::Conditional::Helper::TestBlock.new(2)
32
+
33
+ built = tester.block
34
+
35
+ built.call(1).should be_true
36
+ built.call(2).should_not be_true
37
+
38
+ tester.invoke(1, :ignore).should be_true
39
+ tester.invoke(2, :ignore).should_not be_true
40
+ end
41
+
42
+ it "a TestBlock with a Range" do
43
+ tester = MadderLib::Conditional::Helper::TestBlock.new(2, 4)
44
+
45
+ pound_on do
46
+ built = tester.block
47
+
48
+ built.call(1).should be_true
49
+ built.call(4).should_not be_true
50
+ end
51
+
52
+ tester.invoke(1, :ignore).should be_true
53
+ tester.invoke(4, :ignore).should_not be_true
54
+
55
+ tester = MadderLib::Conditional::Helper::TestBlock.new(Range.new(3, 5))
56
+
57
+ pound_on do
58
+ built = tester.block
59
+
60
+ built.call(2).should be_true
61
+ built.call(5).should_not be_true
62
+ end
63
+
64
+ tester.invoke(2, :ignore).should be_true
65
+ tester.invoke(5, :ignore).should_not be_true
66
+ end
67
+
68
+
69
+
70
+ it "a TestBlock in minutes" do
71
+ tester = MadderLib::Conditional::Helper::TestBlock.new(2, :minute)
72
+
73
+ pound_on do
74
+ built = tester.block
75
+
76
+ # 0, 1 ...
77
+ built.call(1).should be_true
78
+ built.call(2).should_not be_true
79
+ end
80
+
81
+ tester = MadderLib::Conditional::Helper::TestBlock.new(2, 4, :minute)
82
+
83
+ pound_on do
84
+ built = tester.block
85
+
86
+ # 0, 1 ...
87
+ built.call(1).should be_true
88
+ built.call(4).should_not be_true
89
+ end
90
+ end
91
+
92
+
93
+
94
+ it "can convert a TestBlock criterion into an integer" do
95
+ context = MadderLib::Context.new
96
+
97
+ tester = MadderLib::Conditional::Helper::TestBlock.new(1)
98
+ tester.to_i(context).should eql(1)
99
+
100
+ tester = MadderLib::Conditional::Helper::TestBlock.new(1, 2)
101
+ tester.to_i(context).should eql(2)
102
+
103
+ tester = MadderLib::Conditional::Helper::TestBlock.new(Range.new(3, 4))
104
+ tester.to_i(context).should eql(4)
105
+
106
+ tester = MadderLib::Conditional::Helper::TestBlock.new { 2 + 2 }
107
+ tester.to_i(context).should eql(4)
108
+
109
+ context.data[:value] = 5
110
+ tester = MadderLib::Conditional::Helper::TestBlock.new {|context| context.data[:value] }
111
+ tester.to_i(context).should eql(5)
112
+ end
113
+
114
+ it "invokes the contained block, or one explicitly provided" do
115
+ # arity < 1
116
+ tester = MadderLib::Conditional::Helper::TestBlock.new { 2 }
117
+ tester.invoke.should eql(2)
118
+ tester.invoke { 0 }.should eql(0)
119
+
120
+ # arity = 1
121
+ tester = MadderLib::Conditional::Helper::TestBlock.new {|value| value + 1 }
122
+ tester.invoke(1).should eql(2)
123
+ tester.invoke(1) {|value| value - 1 }.should eql(0)
124
+
125
+ # arity = 2, discard ignored
126
+ tester = MadderLib::Conditional::Helper::TestBlock.new {|a, b| a + b }
127
+ tester.invoke(1, 1, :ignored).should eql(2)
128
+ tester.invoke(1, 1, :ignored) {|a, b| a - b }.should eql(0)
129
+ end
130
+
131
+ end
@@ -0,0 +1,138 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Conditional::Likely do
6
+ def distribution(words, map=nil)
7
+ map ||= {}
8
+ words.each do |word|
9
+ map[word] = (map[word] || 0) + 1
10
+ end
11
+
12
+ return map
13
+ end
14
+
15
+
16
+
17
+ it "by default has a 50/50 split" do
18
+ builder = madderlib do
19
+ say 'buffalo'
20
+ it.alternately.says 'fish'
21
+ end
22
+
23
+ refs = %w{ buffalo fish }
24
+ map = nil
25
+ pound_on do
26
+ map = distribution(builder.words, map)
27
+ end
28
+
29
+ # now that'we re done
30
+ # should have hit each once
31
+ # and should have a reasonable distribution (though who can tell)
32
+ map.should have(refs.size).items
33
+ map.keys.each {|key| refs.include?(key).should be_true }
34
+
35
+ map['buffalo'].should be_close(50, 20)
36
+ map['fish'].should be_close(50, 20)
37
+ end
38
+
39
+ it "supports nothing" do
40
+ builder = madderlib do
41
+ say 'something nice'
42
+ alternately.nothing
43
+ end
44
+
45
+ map = nil
46
+ pound_on(100) do
47
+ map = distribution(builder.words, map)
48
+ end
49
+
50
+ # now that'we re done
51
+ # should have hit each once
52
+ # and should have a reasonable distribution (though who can tell)
53
+ map.should have(1).items
54
+ map['something nice'].should_not be_nil
55
+
56
+ map['something nice'].should be_close(50, 20)
57
+ end
58
+
59
+
60
+
61
+ it "getting a 2/1 split" do
62
+ # testing out various alias formats, etc
63
+ builder = madderlib :split_2_1 do
64
+ say 'buffalo'
65
+ it.alternately.says('fish').likely(2)
66
+ end
67
+
68
+ refs = %w{ buffalo fish }
69
+ map = nil
70
+ pound_on do
71
+ words = builder.words
72
+ map = distribution(words, map)
73
+ end
74
+
75
+ # now that'we re done
76
+ # should have hit each once
77
+ # and should have a reasonable distribution (though who can tell)
78
+ map.should have(refs.size).items
79
+ map.keys.each {|key| refs.include?(key).should be_true }
80
+
81
+ map['buffalo'].should be_close(33, 20)
82
+ map['fish'].should be_close(66, 20)
83
+ end
84
+
85
+ it "getting a 3/1 split" do
86
+ # testing out various alias formats, etcnext
87
+ builder = madderlib :split_3_1 do
88
+ say 'buffalo'
89
+ it.alternately.says('fish').weighing { 2 + 1 }
90
+ end
91
+
92
+ refs = %w{ buffalo fish }
93
+ map = nil
94
+ pound_on do
95
+ words = builder.words
96
+ map = distribution(words, map)
97
+ end
98
+
99
+ # now that'we re done
100
+ # should have hit each once
101
+ # and should have a reasonable distribution (though who can tell)
102
+ map.should have(refs.size).items
103
+ map.keys.each {|key| refs.include?(key).should be_true }
104
+
105
+ map['buffalo'].should be_close(25, 20)
106
+ map['fish'].should be_close(75, 20)
107
+ end
108
+
109
+
110
+
111
+ it "getting a 3/2/1 split" do
112
+ # testing out transferrence from 'or' operation to likelihood
113
+ builder = madderlib :split_3_2_1 do
114
+ say 'faith'
115
+ alternately(2).says('hope')
116
+ # range is supported, but not recommended
117
+ it.or(0..3).says('charity')
118
+ end
119
+
120
+ refs = %w{ faith hope charity }
121
+ map = nil
122
+ pound_on 150 do
123
+ words = builder.words
124
+ map = distribution(words, map)
125
+ end
126
+
127
+ # now that'we re done
128
+ # should have hit each once
129
+ # and should have a reasonable distribution (though who can tell)
130
+ map.should have(refs.size).items
131
+ map.keys.each {|key| refs.include?(key).should be_true }
132
+
133
+ map['faith'].should be_close(25, 20)
134
+ map['hope'].should be_close(50, 20)
135
+ map['charity'].should be_close(75, 20)
136
+ end
137
+
138
+ end
@@ -0,0 +1,102 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+
5
+ describe MadderLib::Conditional::Recur do
6
+
7
+ it "only occurs once if not told otherwise" do
8
+ builder = madderlib do
9
+ # give it boundaries
10
+ say(:start)
11
+ anytime.say(:any)
12
+ say(:end)
13
+ end
14
+
15
+ builder.phrases.each do |phrase|
16
+ phrase.recurs?.should be_false if phrase.respond_to?(:recurs?)
17
+ end
18
+
19
+ builder.words.should eql(%w{ start any end })
20
+ end
21
+
22
+ it "supports simple recurrence" do
23
+ builder = madderlib do
24
+ # give it boundaries
25
+ say(:start)
26
+ anytime.recurring(2).say(:any)
27
+ say(:end)
28
+ end
29
+
30
+ builder.phrases.each do |phrase|
31
+ phrase.recurs?.should be_true if phrase.respond_to?(:recurs?)
32
+ end
33
+
34
+ builder.words.should eql(%w{ start any any end })
35
+ end
36
+
37
+ it "supports a 0-recurrence, which blocks the phrase" do
38
+ builder = madderlib do
39
+ say(:start)
40
+ anytime.recur(0).say(:never)
41
+ say(:end)
42
+ end
43
+
44
+ builder.words.should eql(%w{ start end })
45
+ end
46
+
47
+ it "supports Ranged recurrence" do
48
+ builder = madderlib do
49
+ # give it boundaries
50
+ say(:start)
51
+ anytime.recurring(Range.new(3, 5)).say(:few)
52
+ anytime.recurring(3, 5).say(:couple)
53
+ say(:end)
54
+ end
55
+
56
+ pound_on do
57
+ words = builder.words
58
+
59
+ words.shift.should eql('start')
60
+ words.pop.should eql('end')
61
+
62
+ count = 0
63
+ words.each {|word| count += 1 if word == 'few' }
64
+ count.should be_close(4, 1.1)
65
+
66
+ count = 0
67
+ words.each {|word| count += 1 if word == 'couple' }
68
+ count.should be_close(4, 1.1)
69
+ end
70
+ end
71
+
72
+ it "supports arbitrary recurrence" do
73
+ spices = [:parsley, :sage, :rosemary, :thyme]
74
+ shoulds = spices.clone
75
+
76
+ builder = madderlib :repeat_exhausting do
77
+ say(:start)
78
+ # if these don't exist, positioning isn't random
79
+ # it's just a visual confirmation from initial testing
80
+ say(:salt)
81
+ say(:pepper)
82
+ say(:flour)
83
+ say(:end)
84
+
85
+ anytime.recur { ! spices.empty? }.say { spices.shift }
86
+ end
87
+
88
+ words = builder.words
89
+ words.shift.should eql('start')
90
+ words.pop.should eql('end')
91
+
92
+ # and all of the others should be present
93
+ shoulds << :salt << :pepper << :flour
94
+ shoulds.each do |spice|
95
+ words.index(spice.to_s).should_not be_nil
96
+ end
97
+
98
+ # the next pass should be exhausted
99
+ builder.words.should eql(%w{ start salt pepper flour end })
100
+ end
101
+
102
+ end