gullah 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|