scribble 1.0.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 (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