scribble 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +455 -0
  6. data/Rakefile +2 -0
  7. data/lib/scribble.rb +44 -0
  8. data/lib/scribble/block.rb +25 -0
  9. data/lib/scribble/converter.rb +10 -0
  10. data/lib/scribble/errors.rb +24 -0
  11. data/lib/scribble/method.rb +91 -0
  12. data/lib/scribble/methods/if.rb +26 -0
  13. data/lib/scribble/methods/layout.rb +25 -0
  14. data/lib/scribble/methods/partial.rb +14 -0
  15. data/lib/scribble/methods/times.rb +11 -0
  16. data/lib/scribble/nodes/call.rb +55 -0
  17. data/lib/scribble/nodes/ending.rb +6 -0
  18. data/lib/scribble/nodes/node.rb +24 -0
  19. data/lib/scribble/nodes/value.rb +16 -0
  20. data/lib/scribble/objects/boolean.rb +33 -0
  21. data/lib/scribble/objects/fixnum.rb +53 -0
  22. data/lib/scribble/objects/nil.rb +21 -0
  23. data/lib/scribble/objects/string.rb +62 -0
  24. data/lib/scribble/parsing/nester.rb +49 -0
  25. data/lib/scribble/parsing/parser.rb +132 -0
  26. data/lib/scribble/parsing/reporter.rb +71 -0
  27. data/lib/scribble/parsing/transform.rb +87 -0
  28. data/lib/scribble/partial.rb +41 -0
  29. data/lib/scribble/registry.rb +95 -0
  30. data/lib/scribble/support/context.rb +98 -0
  31. data/lib/scribble/support/matcher.rb +74 -0
  32. data/lib/scribble/support/unmatched.rb +70 -0
  33. data/lib/scribble/support/utilities.rb +49 -0
  34. data/lib/scribble/template.rb +61 -0
  35. data/lib/scribble/version.rb +3 -0
  36. data/scribble.gemspec +22 -0
  37. data/test/all.rb +23 -0
  38. data/test/errors_test.rb +94 -0
  39. data/test/methods/if_test.rb +49 -0
  40. data/test/methods/layout_test.rb +71 -0
  41. data/test/methods/partial_test.rb +85 -0
  42. data/test/methods/times_test.rb +10 -0
  43. data/test/objects/boolean_test.rb +162 -0
  44. data/test/objects/fixnum_test.rb +236 -0
  45. data/test/objects/nil_test.rb +83 -0
  46. data/test/objects/string_test.rb +268 -0
  47. data/test/parsing/parsing_test.rb +234 -0
  48. data/test/registry_test.rb +264 -0
  49. data/test/template_test.rb +51 -0
  50. data/test/test_helper.rb +65 -0
  51. metadata +127 -0
@@ -0,0 +1,234 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe Scribble do
4
+ it 'keeps text between tags' do
5
+ assert_scribble_parse 'Hi! Nice template!', "'Hi! Nice template!'"
6
+ assert_scribble_parse 'Hi!{{ }} Nice template!', "'Hi!', ' Nice template!'"
7
+ assert_scribble_parse 'H{{ }}i! Nice template{{}}!', "'H', 'i! Nice template', '!'"
8
+ end
9
+
10
+ it 'parses literal values' do
11
+ assert_scribble_parse '{{ 1 }}', "1"
12
+ assert_scribble_parse '{{ 100 }}', "100"
13
+ assert_scribble_parse "{{ 'foo' }}", "'foo'"
14
+ assert_scribble_parse "{{ 'bar' }}", "'bar'"
15
+ assert_scribble_parse '{{ true }}', "true"
16
+ assert_scribble_parse '{{ false }}', "false"
17
+ end
18
+
19
+ it 'parses binary and unary operators' do
20
+ assert_scribble_parse '{{ 1 | 2 }}', '1.or(2)'
21
+
22
+ assert_scribble_parse '{{ 1 & 2 }}', '1.and(2)'
23
+
24
+ assert_scribble_parse '{{ 1 = 2 }}', '1.equals(2)'
25
+ assert_scribble_parse '{{ 1 != 2 }}', '1.differs(2)'
26
+
27
+ assert_scribble_parse '{{ 1 > 2 }}', '1.greater(2)'
28
+ assert_scribble_parse '{{ 1 < 2 }}', '1.less(2)'
29
+ assert_scribble_parse '{{ 1 >= 2 }}', '1.greater_or_equal(2)'
30
+ assert_scribble_parse '{{ 1 <= 2 }}', '1.less_or_equal(2)'
31
+
32
+ assert_scribble_parse '{{ 1 + 2 }}', '1.add(2)'
33
+ assert_scribble_parse '{{ 1 - 2 }}', '1.subtract(2)'
34
+
35
+ assert_scribble_parse '{{ 1 * 2 }}', '1.multiply(2)'
36
+ assert_scribble_parse '{{ 1 / 2 }}', '1.divide(2)'
37
+ assert_scribble_parse '{{ 1 % 2 }}', '1.remainder(2)'
38
+
39
+ assert_scribble_parse '{{ -1 }}', '1.negative()'
40
+ assert_scribble_parse '{{ !1 }}', '1.not()'
41
+ end
42
+
43
+ it 'parses chains of operators with equal precedence' do
44
+ assert_scribble_parse '{{ 1 | 2 | 3 }}', '1.or(2).or(3)'
45
+
46
+ assert_scribble_parse '{{ 1 & 2 & 3 }}', '1.and(2).and(3)'
47
+
48
+ assert_scribble_parse '{{ 1 = 2 != 3 }}', '1.equals(2).differs(3)'
49
+ assert_scribble_parse '{{ 1 != 2 = 3 }}', '1.differs(2).equals(3)'
50
+
51
+ assert_scribble_parse '{{ 1 > 2 <= 3 }}', '1.greater(2).less_or_equal(3)'
52
+ assert_scribble_parse '{{ 1 < 2 > 3 }}', '1.less(2).greater(3)'
53
+ assert_scribble_parse '{{ 1 >= 2 < 3 }}', '1.greater_or_equal(2).less(3)'
54
+ assert_scribble_parse '{{ 1 <= 2 >= 3 }}', '1.less_or_equal(2).greater_or_equal(3)'
55
+
56
+ assert_scribble_parse '{{ 1 + 2 - 3 }}', '1.add(2).subtract(3)'
57
+ assert_scribble_parse '{{ 1 - 2 + 3 }}', '1.subtract(2).add(3)'
58
+
59
+ assert_scribble_parse '{{ 1 * 2 % 3 }}', '1.multiply(2).remainder(3)'
60
+ assert_scribble_parse '{{ 1 / 2 * 3 }}', '1.divide(2).multiply(3)'
61
+ assert_scribble_parse '{{ 1 % 2 / 3 }}', '1.remainder(2).divide(3)'
62
+
63
+ assert_scribble_parse '{{ --!-1 }}', '1.negative().not().negative().negative()'
64
+ assert_scribble_parse '{{ !!-!1 }}', '1.not().negative().not().not()'
65
+ end
66
+
67
+ it 'combines operators with different precedence' do
68
+ assert_scribble_parse '{{ 1 & 2 | 3 }}', '1.and(2).or(3)'
69
+
70
+ assert_scribble_parse '{{ 1 = 2 & 3 }}', '1.equals(2).and(3)'
71
+ assert_scribble_parse '{{ 1 != 2 & 3 }}', '1.differs(2).and(3)'
72
+
73
+ assert_scribble_parse '{{ 1 > 2 = 3 }}', '1.greater(2).equals(3)'
74
+ assert_scribble_parse '{{ 1 < 2 = 3 }}', '1.less(2).equals(3)'
75
+ assert_scribble_parse '{{ 1 >= 2 = 3 }}', '1.greater_or_equal(2).equals(3)'
76
+ assert_scribble_parse '{{ 1 <= 2 = 3 }}', '1.less_or_equal(2).equals(3)'
77
+ assert_scribble_parse '{{ 1 > 2 != 3 }}', '1.greater(2).differs(3)'
78
+ assert_scribble_parse '{{ 1 < 2 != 3 }}', '1.less(2).differs(3)'
79
+ assert_scribble_parse '{{ 1 >= 2 != 3 }}', '1.greater_or_equal(2).differs(3)'
80
+ assert_scribble_parse '{{ 1 <= 2 != 3 }}', '1.less_or_equal(2).differs(3)'
81
+
82
+ assert_scribble_parse '{{ 1 + 2 > 3 }}', '1.add(2).greater(3)'
83
+ assert_scribble_parse '{{ 1 + 2 < 3 }}', '1.add(2).less(3)'
84
+ assert_scribble_parse '{{ 1 + 2 >= 3 }}', '1.add(2).greater_or_equal(3)'
85
+ assert_scribble_parse '{{ 1 + 2 <= 3 }}', '1.add(2).less_or_equal(3)'
86
+ assert_scribble_parse '{{ 1 - 2 > 3 }}', '1.subtract(2).greater(3)'
87
+ assert_scribble_parse '{{ 1 - 2 < 3 }}', '1.subtract(2).less(3)'
88
+ assert_scribble_parse '{{ 1 - 2 >= 3 }}', '1.subtract(2).greater_or_equal(3)'
89
+ assert_scribble_parse '{{ 1 - 2 <= 3 }}', '1.subtract(2).less_or_equal(3)'
90
+
91
+ assert_scribble_parse '{{ 1 * 2 + 3 }}', '1.multiply(2).add(3)'
92
+ assert_scribble_parse '{{ 1 / 2 + 3 }}', '1.divide(2).add(3)'
93
+ assert_scribble_parse '{{ 1 % 2 + 3 }}', '1.remainder(2).add(3)'
94
+ assert_scribble_parse '{{ 1 * 2 - 3 }}', '1.multiply(2).subtract(3)'
95
+ assert_scribble_parse '{{ 1 / 2 - 3 }}', '1.divide(2).subtract(3)'
96
+ assert_scribble_parse '{{ 1 % 2 - 3 }}', '1.remainder(2).subtract(3)'
97
+ end
98
+
99
+ it 'respects operator precedence' do
100
+ assert_scribble_parse '{{ 1 | 2 & 3 }}', '1.or(2.and(3))'
101
+
102
+ assert_scribble_parse '{{ 1 & 2 = 3 }}', '1.and(2.equals(3))'
103
+ assert_scribble_parse '{{ 1 & 2 != 3 }}', '1.and(2.differs(3))'
104
+
105
+ assert_scribble_parse '{{ 1 = 2 > 3 }}', '1.equals(2.greater(3))'
106
+ assert_scribble_parse '{{ 1 = 2 < 3 }}', '1.equals(2.less(3))'
107
+ assert_scribble_parse '{{ 1 = 2 >= 3 }}', '1.equals(2.greater_or_equal(3))'
108
+ assert_scribble_parse '{{ 1 = 2 <= 3 }}', '1.equals(2.less_or_equal(3))'
109
+ assert_scribble_parse '{{ 1 != 2 > 3 }}', '1.differs(2.greater(3))'
110
+ assert_scribble_parse '{{ 1 != 2 < 3 }}', '1.differs(2.less(3))'
111
+ assert_scribble_parse '{{ 1 != 2 >= 3 }}', '1.differs(2.greater_or_equal(3))'
112
+ assert_scribble_parse '{{ 1 != 2 <= 3 }}', '1.differs(2.less_or_equal(3))'
113
+
114
+ assert_scribble_parse '{{ 1 > 2 + 3 }}', '1.greater(2.add(3))'
115
+ assert_scribble_parse '{{ 1 < 2 + 3 }}', '1.less(2.add(3))'
116
+ assert_scribble_parse '{{ 1 >= 2 + 3 }}', '1.greater_or_equal(2.add(3))'
117
+ assert_scribble_parse '{{ 1 <= 2 + 3 }}', '1.less_or_equal(2.add(3))'
118
+ assert_scribble_parse '{{ 1 > 2 - 3 }}', '1.greater(2.subtract(3))'
119
+ assert_scribble_parse '{{ 1 < 2 - 3 }}', '1.less(2.subtract(3))'
120
+ assert_scribble_parse '{{ 1 >= 2 - 3 }}', '1.greater_or_equal(2.subtract(3))'
121
+ assert_scribble_parse '{{ 1 <= 2 - 3 }}', '1.less_or_equal(2.subtract(3))'
122
+
123
+ assert_scribble_parse '{{ 1 + 2 * 3 }}', '1.add(2.multiply(3))'
124
+ assert_scribble_parse '{{ 1 + 2 / 3 }}', '1.add(2.divide(3))'
125
+ assert_scribble_parse '{{ 1 + 2 % 3 }}', '1.add(2.remainder(3))'
126
+ assert_scribble_parse '{{ 1 - 2 * 3 }}', '1.subtract(2.multiply(3))'
127
+ assert_scribble_parse '{{ 1 - 2 / 3 }}', '1.subtract(2.divide(3))'
128
+ assert_scribble_parse '{{ 1 - 2 % 3 }}', '1.subtract(2.remainder(3))'
129
+
130
+ assert_scribble_parse '{{ -1 * !2 }}', '1.negative().multiply(2.not())'
131
+ assert_scribble_parse '{{ -1 / !2 }}', '1.negative().divide(2.not())'
132
+ assert_scribble_parse '{{ -1 % !2 }}', '1.negative().remainder(2.not())'
133
+ assert_scribble_parse '{{ !1 * -2 }}', '1.not().multiply(2.negative())'
134
+ assert_scribble_parse '{{ !1 / -2 }}', '1.not().divide(2.negative())'
135
+ assert_scribble_parse '{{ !1 % -2 }}', '1.not().remainder(2.negative())'
136
+ end
137
+
138
+ it 'respects operator precedence in complex situations' do
139
+ assert_scribble_parse '{{ !1 / 2 > 3 < -4 * 5 }}', '1.not().divide(2).greater(3).less(4.negative().multiply(5))'
140
+ assert_scribble_parse '{{ 1 | 2 <= !-3 | 4 < 5 }}', '1.or(2.less_or_equal(3.negative().not())).or(4.less(5))'
141
+ assert_scribble_parse '{{ -1 + -2 & 3 != 4 = 5 }}', '1.negative().add(2.negative()).and(3.differs(4).equals(5))'
142
+ assert_scribble_parse '{{ 1 % 2 - 3 >= !4 != !5 }}', '1.remainder(2).subtract(3).greater_or_equal(4.not()).differs(5.not())'
143
+ end
144
+
145
+ it 'respects parentheses' do
146
+ assert_scribble_parse '{{ (1 > 2) = 3 }}', '1.greater(2).equals(3)'
147
+ assert_scribble_parse '{{ (1 < 2) = 3 }}', '1.less(2).equals(3)'
148
+ assert_scribble_parse '{{ (1 >= 2) = 3 }}', '1.greater_or_equal(2).equals(3)'
149
+ assert_scribble_parse '{{ (1 <= 2) = 3 }}', '1.less_or_equal(2).equals(3)'
150
+ assert_scribble_parse '{{ (1 > 2) != 3 }}', '1.greater(2).differs(3)'
151
+ assert_scribble_parse '{{ (1 < 2) != 3 }}', '1.less(2).differs(3)'
152
+ assert_scribble_parse '{{ (1 >= 2) != 3 }}', '1.greater_or_equal(2).differs(3)'
153
+ assert_scribble_parse '{{ (1 <= 2) != 3 }}', '1.less_or_equal(2).differs(3)'
154
+ end
155
+
156
+ # Functions / variables
157
+
158
+ it 'parses names' do
159
+ assert_scribble_parse '{{ foo }}', 'foo'
160
+ assert_scribble_parse '{{ foo123 }}', 'foo123'
161
+ assert_scribble_parse '{{ bar? }}', 'bar?'
162
+ assert_scribble_parse '{{ bar! }}', 'bar!'
163
+ assert_scribble_parse '{{ foo1 + bar2 }}', 'foo1.add(bar2)'
164
+ assert_scribble_parse '{{ foo1 - bar2 }}', 'foo1.subtract(bar2)'
165
+ assert_scribble_parse '{{ foo1 - bar2 / baz3 }}', 'foo1.subtract(bar2.divide(baz3))'
166
+ end
167
+
168
+ it 'parses regular calls' do
169
+ assert_scribble_parse '{{ foo() }}', 'foo()'
170
+ assert_scribble_parse '{{ foo(1) }}', 'foo(1)'
171
+ assert_scribble_parse '{{ bar(1, 2, baz) }}', 'bar(1, 2, baz)'
172
+ assert_scribble_parse '{{ foo(1 + 2, bar()) }}', 'foo(1.add(2), bar())'
173
+ assert_scribble_parse '{{ foo(1) + 2 }}', 'foo(1).add(2)'
174
+ assert_scribble_parse '{{ 1 + foo() }}', '1.add(foo())'
175
+ assert_scribble_parse '{{ 1 + foo(2) }}', '1.add(foo(2))'
176
+ end
177
+
178
+ it 'parses method style calls' do
179
+ assert_scribble_parse '{{ 1.foo(2) }}', '1.foo(2)'
180
+ assert_scribble_parse '{{ 1.foo }}', '1.foo'
181
+ assert_scribble_parse '{{ (1 + 2).foo(3) }}', '1.add(2).foo(3)'
182
+ assert_scribble_parse '{{ (1 + 2.foo(3)).bar(4 + 5, baz) }}', '1.add(2.foo(3)).bar(4.add(5), baz)'
183
+ assert_scribble_parse '{{ (1 + 2).foo(3).bar(4).baz(5) }}', '1.add(2).foo(3).bar(4).baz(5)'
184
+ assert_scribble_parse '{{ (1 - 2).foo.bar.baz(3) }}', '1.subtract(2).foo.bar.baz(3)'
185
+ assert_scribble_parse '{{ -1.foo(2) * !3.bar(4) * 5.baz(6) }}', '1.negative().foo(2).multiply(3.not().bar(4)).multiply(5.baz(6))'
186
+ assert_scribble_parse '{{ -1.foo * !2.bar * 3.baz }}', '1.negative().foo.multiply(2.not().bar).multiply(3.baz)'
187
+ end
188
+
189
+ it 'parses command style calls' do
190
+ assert_scribble_parse '{{ foo 1 }}', 'foo(1)'
191
+ assert_scribble_parse '{{ foo bar 1 }}', 'foo(bar(1))'
192
+ assert_scribble_parse '{{ foo bar baz 1, 2, 3 }}', 'foo(bar(baz(1, 2, 3)))'
193
+ assert_scribble_parse '{{ foo 1, 2, bar }}', 'foo(1, 2, bar)'
194
+ assert_scribble_parse '{{ foo 1 + 2 }}', 'foo(1.add(2))'
195
+ assert_scribble_parse '{{ foo 1 + 2, bar }}', 'foo(1.add(2), bar)'
196
+ assert_scribble_parse '{{ 1 + (foo 2) }}', '1.add(foo(2))'
197
+ assert_scribble_parse '{{ foo 1.bar(2).baz(3) }}', 'foo(1.bar(2).baz(3))'
198
+ assert_scribble_parse '{{ foo(bar 1) }}', 'foo(bar(1))'
199
+ end
200
+
201
+ it 'gives precedence to operations over commands' do
202
+ assert_scribble_parse '{{ foo - 2 }}', 'foo.subtract(2)'
203
+ assert_scribble_parse '{{ (foo - 2) }}', 'foo.subtract(2)'
204
+ assert_scribble_parse '{{ foo(bar - 2) }}', 'foo(bar.subtract(2))'
205
+ assert_scribble_parse '{{ foo bar - 2 }}', 'foo(bar.subtract(2))'
206
+ end
207
+
208
+ it 'parses calls that are both command and method style' do
209
+ assert_scribble_parse '{{ 1.foo 2 }}', '1.foo(2)'
210
+ assert_scribble_parse '{{ -1.foo bar 2 }}', '1.negative().foo(bar(2))'
211
+ assert_scribble_parse '{{ !1.foo bar baz 2, 3, 4 }}', '1.not().foo(bar(baz(2, 3, 4)))'
212
+ assert_scribble_parse '{{ 1.foo 2, 3, bar }}', '1.foo(2, 3, bar)'
213
+ assert_scribble_parse '{{ 1.foo 2 + 3 }}', '1.foo(2.add(3))'
214
+ assert_scribble_parse '{{ -1.foo 2 + 3, bar }}', '1.negative().foo(2.add(3), bar)'
215
+ assert_scribble_parse '{{ !1 + (2.foo 3) }}', '1.not().add(2.foo(3))'
216
+ assert_scribble_parse '{{ 1.foo 2.bar(3).baz(4) }}', '1.foo(2.bar(3).baz(4))'
217
+ assert_scribble_parse '{{ 1.foo(2.bar 3) }}', '1.foo(2.bar(3))'
218
+ end
219
+
220
+ # Nesting
221
+
222
+ it 'nests blocks of nodes in calls that need a block' do
223
+ Scribble::Registry.reset do
224
+ class SomeBlock < Scribble::Block
225
+ def qux; end; register :qux
226
+ end
227
+
228
+ assert_scribble_parse '{{ qux }}foo{{ end }}', "qux { 'foo' }"
229
+ assert_scribble_parse '{{ qux }}{{ qux }}foo{{ end }}{{ end }}', "qux { qux { 'foo' } }"
230
+ assert_scribble_parse '{{ qux }}foo{{ end }}{{ qux }}foo{{ end }}', "qux { 'foo' }, qux { 'foo' }"
231
+ assert_scribble_parse '{{ foo }}{{ qux }}{{ foo }}foo{{ foo }}{{ end }}{{ foo }}', "foo, qux { foo, 'foo', foo }, foo"
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,264 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Scribble::Registry do
4
+ before do
5
+ @registry = Scribble::Registry.new
6
+ end
7
+
8
+ it 'gathers methods' do
9
+ @registry.for String do
10
+ method :foo
11
+ method :bar
12
+ end
13
+
14
+ assert_equal 2, @registry.methods.size
15
+ assert_equal [:foo, :bar], @registry.methods.map(&:method_name)
16
+ end
17
+
18
+ it 'gathers method signatures along with method names' do
19
+ @registry.for String do
20
+ method :foo, String
21
+ method :bar, Fixnum, [String]
22
+ end
23
+
24
+ assert_equal [String], @registry.methods[0].signature
25
+ assert_equal [Fixnum, [String]], @registry.methods[1].signature
26
+ end
27
+
28
+ it 'gathers methods for multiple classes at once' do
29
+ @registry.for TrueClass, FalseClass do
30
+ method :foo
31
+ end
32
+
33
+ assert_equal 2, @registry.methods.size
34
+ assert_equal [TrueClass, FalseClass], @registry.methods.map(&:receiver_class)
35
+ end
36
+
37
+ describe 'methods' do
38
+
39
+ it 'calculates arity' do
40
+ @registry.for String do
41
+ method :foo
42
+ method :bar, String, Fixnum
43
+ method :baz, [String]
44
+ method :qux, String, [String, 3]
45
+ end
46
+
47
+ assert_equal 0, @registry.methods[0].min_arity
48
+ assert_equal 0, @registry.methods[0].max_arity
49
+ assert_equal 2, @registry.methods[1].min_arity
50
+ assert_equal 2, @registry.methods[1].max_arity
51
+ assert_equal 0, @registry.methods[2].min_arity
52
+ assert_equal nil, @registry.methods[2].max_arity
53
+ assert_equal 1, @registry.methods[3].min_arity
54
+ assert_equal 4, @registry.methods[3].max_arity
55
+ end
56
+ end
57
+
58
+ describe 'block and split' do
59
+
60
+ it 'knows if names have block methods' do
61
+ @registry.for String do
62
+ method :foo
63
+ method :bar, block: true
64
+ end
65
+
66
+ assert_equal false, @registry.block?(:foo)
67
+ assert_equal true, @registry.block?(:bar)
68
+ assert_equal nil, @registry.block?(:baz)
69
+ end
70
+
71
+ it 'knows if names have split methods' do
72
+ @registry.for String do
73
+ method :foo
74
+ method :bar, split: true
75
+ end
76
+
77
+ refute @registry.split? :foo
78
+ assert @registry.split? :bar
79
+ end
80
+ end
81
+
82
+ describe 'Rails autoloading support' do
83
+
84
+ it 'can unregister a method by class name' do
85
+ class MyMethod < Scribble::Method
86
+ def bar; end
87
+ setup String, :bar, []
88
+ end
89
+
90
+ MyMethod.insert @registry
91
+
92
+ @registry.for String do
93
+ method :foo
94
+ method :baz
95
+ end
96
+
97
+ assert_equal 3, @registry.methods.size
98
+
99
+ @registry.unregister 'MyMethod'
100
+
101
+ assert_equal 2, @registry.methods.size
102
+ assert_equal [:foo, :baz], @registry.methods.map(&:method_name)
103
+ end
104
+ end
105
+
106
+ describe 'evaluation' do
107
+
108
+ it 'it delegates evaluation to matching methods' do
109
+ @registry.for Fixnum do
110
+ method :foo, returns: 0
111
+ method :foo, String, returns: 1
112
+ method :foo, String, Fixnum, returns: 2
113
+ method :foo, Fixnum, returns: 3
114
+ method :foo, Fixnum, [String], returns: 4
115
+ method :foo, [String, 2], returns: 5
116
+ method :foo, [String], returns: 6
117
+ method :foo, [String], Fixnum, returns: 7
118
+ method :foo, [Object], returns: 8
119
+ end
120
+
121
+ assert_equal 0, @registry.evaluate(:foo, 0, [])
122
+ assert_equal 1, @registry.evaluate(:foo, 0, [''])
123
+ assert_equal 2, @registry.evaluate(:foo, 0, ['', 0])
124
+ assert_equal 3, @registry.evaluate(:foo, 0, [0])
125
+ assert_equal 4, @registry.evaluate(:foo, 0, [0, ''])
126
+ assert_equal 4, @registry.evaluate(:foo, 0, [0, '', ''])
127
+ assert_equal 5, @registry.evaluate(:foo, 0, ['', ''])
128
+ assert_equal 5, @registry.evaluate(:foo, 0, ['', ''])
129
+ assert_equal 6, @registry.evaluate(:foo, 0, ['', '', ''])
130
+ assert_equal 7, @registry.evaluate(:foo, 0, ['', '', 0])
131
+ assert_equal 8, @registry.evaluate(:foo, 0, [false])
132
+ end
133
+
134
+ it 'raises an error when no method matches' do
135
+ @registry.for String do
136
+ method :add, String, to: ->(other) { self + other }
137
+ end
138
+
139
+ assert_raises(Scribble::Support::Unmatched) { @registry.evaluate :add, 'foo', [] }
140
+ assert_raises(Scribble::Support::Unmatched) { @registry.evaluate :addd, 'foo', ['bar'] }
141
+ assert_raises(Scribble::Support::Unmatched) { @registry.evaluate :add, 'foo', ['bar', 'baz'] }
142
+ assert_raises(Scribble::Support::Unmatched) { @registry.evaluate :add, 'foo', [1] }
143
+ assert_raises(Scribble::Support::Unmatched) { @registry.evaluate :add, 1, ['foo'] }
144
+ end
145
+
146
+ it 'provides different implementation options for methods' do
147
+ @registry.for TrueClass do
148
+ method :baz, Object, to: ->(object) { object }
149
+ end
150
+
151
+ @registry.for String do
152
+ to_boolean { true }
153
+ method :foo, Object, as: '=='
154
+ method :bar, Object, to: ->(object) { object }
155
+ method :baz, Object, cast: 'to_boolean'
156
+ method :qux, Object, returns: 'qux'
157
+ end
158
+
159
+ assert_equal true, @registry.evaluate(:foo, 'foo', ['foo'])
160
+ assert_equal false, @registry.evaluate(:foo, 'foo', ['bar'])
161
+ assert_equal 'baz', @registry.evaluate(:bar, 'foo', ['baz'])
162
+ assert_equal 'baz', @registry.evaluate(:baz, 'foo', ['baz'])
163
+ assert_equal 'qux', @registry.evaluate(:qux, 'foo', ['baz'])
164
+ end
165
+
166
+ it 'gathers and evaluates to_boolean cast methods' do
167
+ @registry.for String do
168
+ to_boolean { self == 'foo' }
169
+ end
170
+
171
+ @registry.for Fixnum do
172
+ to_boolean { self > 0 }
173
+ end
174
+
175
+ assert_equal 2, @registry.methods.size
176
+
177
+ assert_equal @registry.to_boolean('foo'), true
178
+ assert_equal @registry.to_boolean('bar'), false
179
+ assert_equal @registry.to_boolean(1), true
180
+ assert_equal @registry.to_boolean(0), false
181
+ end
182
+
183
+ it 'gathers and evaluated to_string cast methods' do
184
+ @registry.for String do
185
+ to_string { self }
186
+ end
187
+
188
+ @registry.for Fixnum do
189
+ to_string { to_s }
190
+ end
191
+
192
+ assert_equal 2, @registry.methods.size
193
+
194
+ assert_equal @registry.to_string('foo'), 'foo'
195
+ assert_equal @registry.to_string(12345), '12345'
196
+ end
197
+ end
198
+
199
+ describe 'sanity checks' do
200
+
201
+ it 'makes sure method names are symbols' do
202
+ assert_raises_message(/needs to be a Symbol/) { @registry.for(String) { method 'foo' } }
203
+ end
204
+
205
+ it 'does not allow methods with duplicate class, name and signature' do
206
+ @registry.for String do
207
+ method :foo
208
+ method :foo, String
209
+ method :bar, String
210
+ end
211
+
212
+ @registry.for Fixnum do
213
+ method :foo
214
+ end
215
+
216
+ assert_raises_message(/Duplicate method/) { @registry.for(String) { method :foo } }
217
+ assert_raises_message(/Duplicate method/) { @registry.for(String) { method :bar, String } }
218
+ assert_raises_message(/Duplicate method/) { @registry.for(Fixnum) { method :foo } }
219
+
220
+ assert_equal 4, @registry.methods.size
221
+ end
222
+
223
+ it 'only allows a single implementation option per method' do
224
+ assert_raises_message(/multiple implementation options/) { @registry.for(String) { method :foo, as: 'bar', to: -> {} } }
225
+ end
226
+
227
+ it 'checks the type of implementation options and split' do
228
+ assert_raises_message(/requires String/) { @registry.for(String) { method :foo, as: :bar } }
229
+ assert_raises_message(/requires Proc/) { @registry.for(String) { method :foo, to: :bar } }
230
+ assert_raises_message(/:cast must be/) { @registry.for(String) { method :foo, cast: 'to_float' } }
231
+ assert_raises_message(/:split must be/) { @registry.for(String) { method :foo, split: :true } }
232
+ end
233
+
234
+ it 'requires methods with the same name to always or never be a block' do
235
+ @registry.for String do
236
+ method :foo
237
+ method :bar, block: true
238
+ end
239
+
240
+ assert_raises_message(/must be a non-block/) { @registry.for(String) { method :foo, String, block: true } }
241
+ assert_raises_message(/must be a block/) { @registry.for(String) { method :bar, String } }
242
+
243
+ assert_raises_message(/must be a non-block/) { @registry.for(Fixnum) { method :foo, block: true } }
244
+ assert_raises_message(/must be a block/) { @registry.for(Fixnum) { method :bar } }
245
+
246
+ assert_equal 2, @registry.methods.size
247
+ end
248
+
249
+ it 'requires methods with the same name to always or never be splits' do
250
+ @registry.for String do
251
+ method :foo
252
+ method :bar, split: true
253
+ end
254
+
255
+ assert_raises_message(/must be a non-split/) { @registry.for(String) { method :foo, String, split: true } }
256
+ assert_raises_message(/must be a split/) { @registry.for(String) { method :bar, String } }
257
+
258
+ assert_raises_message(/must be a non-split/) { @registry.for(Fixnum) { method :foo, split: true } }
259
+ assert_raises_message(/must be a split/) { @registry.for(Fixnum) { method :bar } }
260
+
261
+ assert_equal 2, @registry.methods.size
262
+ end
263
+ end
264
+ end