gullah 0.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/LICENSE +21 -0
- data/README.md +87 -0
- data/Rakefile +11 -0
- data/TODO.md +2 -0
- data/examples/hat.rb +27 -0
- data/examples/trash.rb +42 -0
- data/examples/xml.rb +45 -0
- data/gullah.gemspec +31 -0
- data/lib/gullah/atom.rb +132 -0
- data/lib/gullah/boundary.rb +11 -0
- data/lib/gullah/dotifier.rb +127 -0
- data/lib/gullah/error.rb +7 -0
- data/lib/gullah/hopper.rb +142 -0
- data/lib/gullah/iterator.rb +67 -0
- data/lib/gullah/leaf.rb +24 -0
- data/lib/gullah/node.rb +553 -0
- data/lib/gullah/parse.rb +233 -0
- data/lib/gullah/picker.rb +56 -0
- data/lib/gullah/rule.rb +90 -0
- data/lib/gullah/segment.rb +92 -0
- data/lib/gullah/trash.rb +15 -0
- data/lib/gullah/version.rb +7 -0
- data/lib/gullah.rb +777 -0
- data/test/basic_test.rb +451 -0
- data/test/big_tree_test.rb +26 -0
- data/test/boundary_test.rb +29 -0
- data/test/date_test.rb +111 -0
- data/test/error_test.rb +245 -0
- data/test/json_test.rb +124 -0
- data/test/parse_demo_test.rb +33 -0
- data/test/precondition_test.rb +68 -0
- data/test/tests_per_subrule_test.rb +49 -0
- data/test/tree_walking_test.rb +88 -0
- metadata +157 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
require 'date'
|
8
|
+
|
9
|
+
# :stopdoc:
|
10
|
+
|
11
|
+
# a test to make sure boundary rules work
|
12
|
+
class BoundaryTest < Minitest::Test
|
13
|
+
class Bounded
|
14
|
+
extend Gullah
|
15
|
+
|
16
|
+
rule :S, 'word+'
|
17
|
+
|
18
|
+
leaf :word, /\w+/
|
19
|
+
boundary :term, /[.!?](?=\s*\z|\s+"?\p{Lu})|[:;]/
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_example
|
23
|
+
parses = Bounded.parse 'One sentence. Another sentence.'
|
24
|
+
assert_equal 1, parses.length, 'Got one parse.'
|
25
|
+
parse = parses.first
|
26
|
+
assert_equal 5, parse.length, 'One node per sentence plus one per boundary plus one space.'
|
27
|
+
assert_equal 2, parse.nodes.count(&:boundary?), 'There are two boundary nodes.'
|
28
|
+
end
|
29
|
+
end
|
data/test/date_test.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
require 'date'
|
8
|
+
|
9
|
+
# :stopdoc:
|
10
|
+
|
11
|
+
class DateTest < Minitest::Test
|
12
|
+
class DateGrammar
|
13
|
+
extend Gullah
|
14
|
+
|
15
|
+
rule :iso, 'year "/" month "/" day', tests: %i[sane]
|
16
|
+
rule :american, 'month "/" day "/" year', tests: %i[sane]
|
17
|
+
rule :euro, 'day "/" month "/" year', tests: %i[sane]
|
18
|
+
|
19
|
+
leaf :day, /\b\d{1,2}\b/, tests: %i[day], process: :to_i
|
20
|
+
leaf :month, /\b\d{1,2}\b/, tests: %i[month], process: :to_i
|
21
|
+
# to confirm that we can pass a proc as a processor
|
22
|
+
leaf :year, /\b\d+\b/, process: ->(n) { n.atts[:value] = n.text.to_i }
|
23
|
+
|
24
|
+
def to_i(n)
|
25
|
+
n.atts[:value] = n.text.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def month(root, n)
|
29
|
+
if root == n.parent
|
30
|
+
month = n.atts[:value]
|
31
|
+
if month < 1
|
32
|
+
[:fail, 'month must be greater than 0']
|
33
|
+
elsif month > 12
|
34
|
+
[:fail, 'month cannot be greater than 12']
|
35
|
+
else
|
36
|
+
:pass
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def day(root, n)
|
42
|
+
if root == n.parent
|
43
|
+
day = n.atts[:value]
|
44
|
+
if day < 1
|
45
|
+
[:fail, 'day must be greater than 0']
|
46
|
+
elsif day > 31
|
47
|
+
[:fail, 'day cannot be greater than 31']
|
48
|
+
else
|
49
|
+
:pass
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def sane(n)
|
55
|
+
day = n.descendants.find { |o| o.name == :day }
|
56
|
+
month = n.descendants.find { |o| o.name == :month }
|
57
|
+
year = n.descendants.find { |o| o.name == :year }
|
58
|
+
if day && month && year
|
59
|
+
begin
|
60
|
+
Date.new year.atts[:value], month.atts[:value], day.atts[:value]
|
61
|
+
rescue ArgumentError
|
62
|
+
return [
|
63
|
+
:fail,
|
64
|
+
"month #{month.text} does not have a day #{day.text} in #{year.text}"
|
65
|
+
]
|
66
|
+
end
|
67
|
+
:pass
|
68
|
+
else
|
69
|
+
[:fail, "we don't have all parts of a date"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_iso
|
75
|
+
parses = DateGrammar.parse '2010/5/6'
|
76
|
+
assert_equal 1, parses.length, 'one parse'
|
77
|
+
parse = parses.first
|
78
|
+
assert_equal 1, parse.roots.length, 'one root node'
|
79
|
+
root = parse.roots.first
|
80
|
+
assert_equal :iso, root.name, 'got an iso date'
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_american
|
84
|
+
parses = DateGrammar.parse '10/31/2021'
|
85
|
+
assert_equal 1, parses.length, 'one parse'
|
86
|
+
parse = parses.first
|
87
|
+
assert_equal 1, parse.roots.length, 'one root node'
|
88
|
+
root = parse.roots.first
|
89
|
+
assert_equal :american, root.name, 'got an American date'
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_euro
|
93
|
+
parses = DateGrammar.parse '31/10/2021'
|
94
|
+
assert_equal 1, parses.length, 'one parse'
|
95
|
+
parse = parses.first
|
96
|
+
assert_equal 1, parse.roots.length, 'one root node'
|
97
|
+
root = parse.roots.first
|
98
|
+
assert_equal :euro, root.name, 'got a euro date'
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_ambiguous
|
102
|
+
parses = DateGrammar.parse '5/6/1969'
|
103
|
+
assert_equal 2, parses.length, 'two parses'
|
104
|
+
options = %i[euro american]
|
105
|
+
parses.each do |p|
|
106
|
+
assert_equal 1, p.roots.length
|
107
|
+
options -= [p.roots.first.name]
|
108
|
+
end
|
109
|
+
assert_equal [], options, 'one is american and one euro'
|
110
|
+
end
|
111
|
+
end
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
|
10
|
+
# tests that all the errors that should be raised are raised
|
11
|
+
class ErrorTest < Minitest::Test
|
12
|
+
class NoLeaf
|
13
|
+
extend Gullah
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_no_leaves
|
17
|
+
e = assert_raises Gullah::Error, 'leaves required' do
|
18
|
+
NoLeaf.parse 'foo'
|
19
|
+
end
|
20
|
+
assert_match(/no leaves/, e.message, 'expected no-leaf message')
|
21
|
+
end
|
22
|
+
|
23
|
+
class UndefinedRules1
|
24
|
+
extend Gullah
|
25
|
+
|
26
|
+
rule :foo, 'bar'
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_undefined_rules_1
|
30
|
+
e = assert_raises Gullah::Error, 'some rules undefined' do
|
31
|
+
UndefinedRules1.parse 'bar'
|
32
|
+
end
|
33
|
+
assert_match(/no leaves/, e.message, 'remain undefined')
|
34
|
+
end
|
35
|
+
|
36
|
+
class UndefinedRules2
|
37
|
+
extend Gullah
|
38
|
+
|
39
|
+
rule :foo, 'bar baz'
|
40
|
+
leaf :bar, /bar/
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_undefined_rules_2
|
44
|
+
e = assert_raises Gullah::Error, 'some rules undefined' do
|
45
|
+
UndefinedRules2.parse 'bar'
|
46
|
+
end
|
47
|
+
assert_match(/remain undefined/, e.message, 'remain undefined')
|
48
|
+
end
|
49
|
+
|
50
|
+
class AddAfterParse1
|
51
|
+
extend Gullah
|
52
|
+
|
53
|
+
rule :foo, 'bar baz'
|
54
|
+
leaf :bar, /bar/
|
55
|
+
leaf :baz, /baz/
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_add_after_parse_1
|
59
|
+
e = assert_raises Gullah::Error, 'definition after parse' do
|
60
|
+
AddAfterParse1.parse 'bar baz'
|
61
|
+
AddAfterParse1.rule :plugh, 'plugh'
|
62
|
+
end
|
63
|
+
assert_match(/must be defined before parsing/, e.message, 'cannot define rule after parsing')
|
64
|
+
end
|
65
|
+
|
66
|
+
class AddAfterParse2
|
67
|
+
extend Gullah
|
68
|
+
|
69
|
+
rule :foo, 'bar baz'
|
70
|
+
leaf :bar, /bar/
|
71
|
+
leaf :baz, /baz/
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_add_after_parse_2
|
75
|
+
e = assert_raises Gullah::Error, 'definition after parse' do
|
76
|
+
AddAfterParse2.parse 'bar baz'
|
77
|
+
AddAfterParse2.leaf :plugh, /plugh/
|
78
|
+
end
|
79
|
+
assert_match(/must be defined before parsing/, e.message, 'cannot define leaf after parsing')
|
80
|
+
end
|
81
|
+
|
82
|
+
class UndefinedTest
|
83
|
+
extend Gullah
|
84
|
+
|
85
|
+
rule :foo, 'bar baz', tests: %i[undefined]
|
86
|
+
leaf :bar, /bar/
|
87
|
+
leaf :baz, /baz/
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_undefined_test
|
91
|
+
e = assert_raises Gullah::Error, 'undefined test' do
|
92
|
+
UndefinedTest.parse 'bar baz'
|
93
|
+
end
|
94
|
+
assert_match(/is not defined/, e.message, 'must define tests')
|
95
|
+
end
|
96
|
+
|
97
|
+
class UndefinedProcessor
|
98
|
+
extend Gullah
|
99
|
+
|
100
|
+
rule :foo, 'bar baz', process: :undefined
|
101
|
+
leaf :bar, /bar/
|
102
|
+
leaf :baz, /baz/
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_undefined_processor
|
106
|
+
e = assert_raises Gullah::Error, 'undefined processor' do
|
107
|
+
UndefinedProcessor.parse 'bar baz'
|
108
|
+
end
|
109
|
+
assert_match(/is not defined/, e.message, 'must define processors')
|
110
|
+
end
|
111
|
+
|
112
|
+
class UndefinedPrecondition
|
113
|
+
extend Gullah
|
114
|
+
|
115
|
+
rule :foo, 'bar baz', preconditions: [:undefined]
|
116
|
+
leaf :bar, /bar/
|
117
|
+
leaf :baz, /baz/
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_undefined_precondition
|
121
|
+
e = assert_raises Gullah::Error, 'undefined precondition' do
|
122
|
+
UndefinedPrecondition.parse 'bar baz'
|
123
|
+
end
|
124
|
+
assert_match(/is not defined/, e.message, 'must define preconditions')
|
125
|
+
end
|
126
|
+
|
127
|
+
class BadTest
|
128
|
+
extend Gullah
|
129
|
+
|
130
|
+
rule :foo, 'bar baz', tests: %i[bad]
|
131
|
+
leaf :bar, /bar/
|
132
|
+
leaf :baz, /baz/
|
133
|
+
|
134
|
+
def bad
|
135
|
+
puts 'I have no arguments at all!'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_zero_arity
|
140
|
+
e = assert_raises Gullah::Error, 'arity 0' do
|
141
|
+
BadTest.parse 'bar baz'
|
142
|
+
end
|
143
|
+
assert_match(/must take either one or two arguments/, e.message, 'needs one arg')
|
144
|
+
end
|
145
|
+
|
146
|
+
class AlsoBadTest
|
147
|
+
extend Gullah
|
148
|
+
|
149
|
+
rule :foo, 'bar baz', tests: %i[bad]
|
150
|
+
leaf :bar, /bar/
|
151
|
+
leaf :baz, /baz/
|
152
|
+
|
153
|
+
def bad(_root, _node, _other, _things)
|
154
|
+
puts 'I have too many arguments!'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_excessive_arity
|
159
|
+
e = assert_raises Gullah::Error, 'arity many' do
|
160
|
+
AlsoBadTest.parse 'bar baz'
|
161
|
+
end
|
162
|
+
assert_match(/must take either one or two arguments/, e.message, 'no more than 2 args')
|
163
|
+
end
|
164
|
+
|
165
|
+
class MisnamedRule
|
166
|
+
extend Gullah
|
167
|
+
|
168
|
+
leaf :'bar@', /bar/
|
169
|
+
leaf :baz, /baz/
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_misnamed_rule
|
173
|
+
e = assert_raises Gullah::Error, 'rule name' do
|
174
|
+
MisnamedRule.rule :foo, 'bar@ baz'
|
175
|
+
end
|
176
|
+
assert_match(/cannot parse/, e.message, 'bad rule name')
|
177
|
+
end
|
178
|
+
|
179
|
+
class BadSuffix
|
180
|
+
extend Gullah
|
181
|
+
|
182
|
+
leaf :bar, /bar/
|
183
|
+
leaf :baz, /baz/
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_bad_suffix_rule
|
187
|
+
e = assert_raises Gullah::Error, 'rule suffix' do
|
188
|
+
MisnamedRule.rule :foo, 'bar{2,1} baz'
|
189
|
+
end
|
190
|
+
assert_match(/is greater than/, e.message, 'bad suffix')
|
191
|
+
end
|
192
|
+
|
193
|
+
class Decent
|
194
|
+
extend Gullah
|
195
|
+
|
196
|
+
rule :foo, 'bar baz'
|
197
|
+
leaf :bar, /bar/
|
198
|
+
leaf :baz, /baz/
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_filters
|
202
|
+
e = assert_raises Gullah::Error, 'unknown filter' do
|
203
|
+
Decent.parse 'bar baz', filters: %i[foo]
|
204
|
+
end
|
205
|
+
assert_match(/unknown filter/, e.message)
|
206
|
+
end
|
207
|
+
|
208
|
+
class BadTestReturnValue
|
209
|
+
extend Gullah
|
210
|
+
|
211
|
+
rule :foo, 'bar baz', tests: %i[foo]
|
212
|
+
leaf :bar, /bar/
|
213
|
+
leaf :baz, /baz/
|
214
|
+
|
215
|
+
def foo(_n)
|
216
|
+
:foo
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_test_return_value
|
221
|
+
e = assert_raises Gullah::Error, 'bad test return value' do
|
222
|
+
BadTestReturnValue.parse 'bar baz'
|
223
|
+
end
|
224
|
+
assert_match(/unexpected value/, e.message)
|
225
|
+
end
|
226
|
+
|
227
|
+
class BadAncestorTestReturnValue
|
228
|
+
extend Gullah
|
229
|
+
|
230
|
+
rule :foo, 'bar baz'
|
231
|
+
leaf :bar, /bar/, tests: %i[foo]
|
232
|
+
leaf :baz, /baz/
|
233
|
+
|
234
|
+
def foo(_root, _n)
|
235
|
+
:foo
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_ancestor_test_return_value
|
240
|
+
e = assert_raises Gullah::Error, 'bad ancestor test return value' do
|
241
|
+
BadAncestorTestReturnValue.parse 'bar baz'
|
242
|
+
end
|
243
|
+
assert_match(/unexpected value/, e.message)
|
244
|
+
end
|
245
|
+
end
|
data/test/json_test.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
# :stopdoc:
|
10
|
+
|
11
|
+
# a proof of concept JSON parser
|
12
|
+
class JsonTest < Minitest::Test
|
13
|
+
class Gson
|
14
|
+
extend Gullah
|
15
|
+
|
16
|
+
# NOTE: these rules have processors to simplify testing
|
17
|
+
# this is *not* the most efficient way to deserialize the JSON string
|
18
|
+
# better would be to convert the AST after parsing
|
19
|
+
|
20
|
+
rule :object, '"{" key_value_pair* last_pair | empty_object', process: :objectify
|
21
|
+
rule :last_pair, 'key ":" json following_brace', process: :inherit_json_value
|
22
|
+
rule :key_value_pair, 'key ":" json ","', process: :inherit_json_value
|
23
|
+
rule :array, '"[" array_item* json? "]"', process: :arrayify
|
24
|
+
rule :json, 'complex | simple', process: :inherit_value
|
25
|
+
rule :complex, 'array | object', process: :inherit_value
|
26
|
+
rule :array_item, 'json ","', process: :inherit_value
|
27
|
+
rule :simple, 'string | null | integer | si | float | boolean', process: :inherit_value
|
28
|
+
|
29
|
+
leaf :boolean, /\b(true|false)\b/, process: ->(n) { n.atts[:value] = n.text == 'true' }
|
30
|
+
leaf :string, /'(?:[^'\\]|\\.)*'(?!\s*:)/, process: :clean_string
|
31
|
+
leaf :string, /"(?:[^"\\]|\\.)*"(?!\s*:)/, process: :clean_string
|
32
|
+
leaf :null, /\bnull\b/, process: ->(n) { n.atts[:value] = nil }
|
33
|
+
leaf :si, /\b\d\.\d+e[1-9]\d*\b/, process: ->(n) { n.atts[:value] = n.text.to_f }
|
34
|
+
leaf :float, /\b\d+\.\d+\b/, process: ->(n) { n.atts[:value] = n.text.to_f }
|
35
|
+
leaf :integer, /\b[1-9]\d*\b(?!\.\d)/, process: ->(n) { n.atts[:value] = n.text.to_i }
|
36
|
+
|
37
|
+
# terrible, horrible, no good, very bad hacks to reduce backtracking
|
38
|
+
leaf :following_brace, /}/
|
39
|
+
leaf :empty_object, /\{\s*\}/
|
40
|
+
leaf :key, /'(?:[^'\\]|\\.)*'(?=\s*:)/, process: :clean_string
|
41
|
+
leaf :key, /"(?:[^"\\]|\\.)*"(?=\s*:)/, process: :clean_string
|
42
|
+
|
43
|
+
def inherit_json_value(node)
|
44
|
+
node.atts[:value] = node.children.find { |n| n.name == :json }.atts[:value]
|
45
|
+
end
|
46
|
+
|
47
|
+
def inherit_value(node)
|
48
|
+
node.atts[:value] = node.children.first.atts[:value]
|
49
|
+
end
|
50
|
+
|
51
|
+
def clean_string(node)
|
52
|
+
text = node.text
|
53
|
+
node.atts[:value] = text[1...(text.length - 1)].gsub(/\\(.)/, '\1')
|
54
|
+
end
|
55
|
+
|
56
|
+
def arrayify(node)
|
57
|
+
node.atts[:value] = node.children.reject(&:leaf?).map do |n|
|
58
|
+
n.subtree.find { |c| c.name == :json }.atts[:value]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def objectify(node)
|
63
|
+
node.atts[:value] = if node.children.first.name == :empty_object
|
64
|
+
{}
|
65
|
+
else
|
66
|
+
node.children.reject(&:leaf?).map do |pair|
|
67
|
+
key, _, value = pair.children
|
68
|
+
[key.atts[:value], value.atts[:value]]
|
69
|
+
end.to_h
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_various
|
75
|
+
[
|
76
|
+
[],
|
77
|
+
{},
|
78
|
+
1,
|
79
|
+
1.1,
|
80
|
+
1.2345678901e10,
|
81
|
+
'string',
|
82
|
+
'"string"',
|
83
|
+
[1],
|
84
|
+
{ 'a' => 1 },
|
85
|
+
{ 'a' => 1, 'b' => 2 },
|
86
|
+
['2', { 'a' => false }],
|
87
|
+
[1, nil, '2', { 'a' => false }]
|
88
|
+
].each do |val|
|
89
|
+
json = JSON.unparse(val)
|
90
|
+
parses = clock(json) { Gson.parse json }
|
91
|
+
assert_equal 1, parses.length, "unambiguous: #{json}"
|
92
|
+
parse = parses.first
|
93
|
+
root = parse.roots.first
|
94
|
+
assert_equal val, root.atts[:value], "parsed value correctly: #{json}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# more complex patterns
|
99
|
+
def test_monsters
|
100
|
+
[
|
101
|
+
[[[[1, 2, 3]]]],
|
102
|
+
{ 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => 1 } } } } } } } } },
|
103
|
+
{ 'foo' => [1, 2, true], 'bar' => ['baz'], 'baz' => { 'v1' => nil, 'v2' => [], 'v3' => 'corge' } },
|
104
|
+
[{ 'foo bar' => 1, 'baz' => ['a string with a lot of spaces in it'] }]
|
105
|
+
].each do |val|
|
106
|
+
json = JSON.unparse(val)
|
107
|
+
parse = clock(json) { Gson.first json }
|
108
|
+
root = parse.roots.first
|
109
|
+
assert_equal val, root.atts[:value], "parsed value correctly: #{json}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# for catching really slow stuff
|
116
|
+
def clock(id)
|
117
|
+
t1 = Time.now
|
118
|
+
value = yield
|
119
|
+
t2 = Time.now
|
120
|
+
delta = t2.to_f - t1.to_f
|
121
|
+
puts "\n#{id}: #{delta} seconds" if delta > 1
|
122
|
+
value
|
123
|
+
end
|
124
|
+
end
|