xel 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +26 -0
- data/README.md +265 -0
- data/lib/xel/evaluator.rb +530 -0
- data/lib/xel/parser.rb +112 -0
- data/lib/xel.rb +25 -0
- data/xel.gemspec +46 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aff030adf152ce449c4cc1680640329458dfc4f53388af9218f3165fbe67e05b
|
4
|
+
data.tar.gz: e654443aeda26b28abad25e9be066132924415cab58b26a31b83e8121e421bc3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c289c6eedf021d9065d6fd3169c4a8886e4a24765b80c7861a99485eba512fe88afa22de4766422e8cc16fcb36f0a541065d3d7e6bee1264e58b6ce39bea5501
|
7
|
+
data.tar.gz: 7f108af095671d99a9a58b47a35c4c561e61206f554872f85b4282f2aeb6e1147611b3f875235738e87cdb75357bcfb3de153c81a9fe39654f0fa0edddd1ca4e
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Copyright (c) 2015-2024, John Mettraux, jmettraux@gmail.com
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# Made in Japan
|
25
|
+
#
|
26
|
+
|
data/README.md
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
|
2
|
+
# xel.rb
|
3
|
+
|
4
|
+
Interpreting expressions built out of a subset of spreadsheet functions.
|
5
|
+
|
6
|
+
Twin to [xel.js](https://github.com/jmettraux/xel.js).
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
require 'xel'
|
10
|
+
|
11
|
+
Xel.eval(" = CASE(a<5000000, 0.6, a<10000000, 0.55, 0.45)", { a: 10000 })
|
12
|
+
# --> 0.6
|
13
|
+
|
14
|
+
Xel.eval("CASE(a<5000000, 0.6, a<10000000, 0.55, 0.45)", { a: 5_100_000 })
|
15
|
+
# --> 0.55
|
16
|
+
```
|
17
|
+
|
18
|
+
Here are the eval tests, in the format `code [ ⟶ context] ⟶ result`
|
19
|
+
|
20
|
+
```
|
21
|
+
987 ⟶ 987
|
22
|
+
3,000,000 ⟶ 3_000_000
|
23
|
+
123.45 ⟶ 123.45
|
24
|
+
9,090.10 ⟶ 9_090.1
|
25
|
+
.123 ⟶ 0.123
|
26
|
+
-987 ⟶ -987
|
27
|
+
-3,000,000 ⟶ -3_000_000
|
28
|
+
-123.45 ⟶ -123.45
|
29
|
+
-9,090.10 ⟶ -9_090.10
|
30
|
+
-.123 ⟶ -0.123
|
31
|
+
|
32
|
+
"12" =~ "[a-z]" ⟶ false
|
33
|
+
"a" != "a" ⟶ false
|
34
|
+
"a" != "b" ⟶ true
|
35
|
+
"a" = "a" ⟶ true
|
36
|
+
"a" = "b" ⟶ false
|
37
|
+
"a" IN { 0, "a", 2 } ⟶ true
|
38
|
+
"a" IN { 0, "b", 2 } ⟶ false
|
39
|
+
"a" IN {} ⟶ false
|
40
|
+
"ab" =~ "[a-z]" ⟶ true
|
41
|
+
|
42
|
+
"b" IN a ⟶ {"a":["a","b","c"]} ⟶ true
|
43
|
+
"d" IN a ⟶ {"a":["a","b","c"]} ⟶ false
|
44
|
+
|
45
|
+
{ "a" } != { "a" } ⟶ false
|
46
|
+
{ "a" } != { "b" } ⟶ true
|
47
|
+
{ "a" } = { "a" } ⟶ true
|
48
|
+
{ "a" } = { "b" } ⟶ false
|
49
|
+
{ "a", "b" } != { "a" } ⟶ true
|
50
|
+
{ "a", "b" } != { "a", "b" } ⟶ false
|
51
|
+
{ "a", "b" } = { "a" } ⟶ false
|
52
|
+
{ "a", "b" } = { "a", "b" } ⟶ true
|
53
|
+
{ 1, "a", "b", "c" } ⟶ [ 1, "a", "b", "c" ]
|
54
|
+
{ 1, 2 } + { "a", "b" } ⟶ [ 1, 2, "a", "b" ]
|
55
|
+
{ { "a", "b" }, { "c", "de" } } ⟶ [ [ "a", "b" ], [ "c", "de" ] ]
|
56
|
+
{ { "a", 1 }, { "c", 2 } } ⟶ [ [ "a", 1 ], [ "c", 2 ] ]
|
57
|
+
{ "a", "b", "c" } ⟶ [ "a", "b", "c" ]
|
58
|
+
{ "a","b" } ⟶ [ "a", "b" ]
|
59
|
+
|
60
|
+
LET(price, 100, count, 3, count * price) ⟶ 300
|
61
|
+
LET(price, 95, count, 3, 12, count * price) ⟶ 285
|
62
|
+
LET(price, 13, LOWER("COUNT"), 3, count * price) ⟶ 39
|
63
|
+
LET(l, LAMBDA(a, b, a + b), l(2, 3)) ⟶ 5
|
64
|
+
LET(_l, LAMBDA(a, b, a * b), _l(2, 3)) ⟶ 6
|
65
|
+
|
66
|
+
ROUND(3.14159) ⟶ 3
|
67
|
+
ROUND(3.14159, 0) ⟶ 3
|
68
|
+
ROUND(3.14159, 2) ⟶ 3.14
|
69
|
+
ROUND(157, -1) ⟶ 160
|
70
|
+
ROUND(157, -2) ⟶ 200
|
71
|
+
ROUND(2.678, 1) ⟶ 2.7
|
72
|
+
|
73
|
+
MROUND(51, 3) ⟶ 51
|
74
|
+
MROUND(50, 7) ⟶ 49
|
75
|
+
MROUND(50, 3) ⟶ 51
|
76
|
+
MROUND(10, 3) ⟶ 9
|
77
|
+
MROUND(10, -3) ⟶ nil
|
78
|
+
MROUND(-10, 3) ⟶ nil
|
79
|
+
MROUND(-10, -3) ⟶ -9
|
80
|
+
MROUND(0, 3) ⟶ 0
|
81
|
+
MROUND(10, 0) ⟶ nil
|
82
|
+
MROUND(10.5, 0.3) ⟶ 10.5
|
83
|
+
MROUND(5, 3) ⟶ 6
|
84
|
+
MROUND(4, 3) ⟶ 3
|
85
|
+
MROUND(10.5678, 0.05) ⟶ 10.55
|
86
|
+
|
87
|
+
# need MROUND2 for the job, but why again?
|
88
|
+
#
|
89
|
+
MROUND2(51, 3) ⟶ 51
|
90
|
+
MROUND2(50, 7) ⟶ 49
|
91
|
+
MROUND2(50, 3) ⟶ 51
|
92
|
+
MROUND2(10, 3) ⟶ 9
|
93
|
+
MROUND2(10, -3) ⟶ nil
|
94
|
+
MROUND2(-10, 3) ⟶ nil
|
95
|
+
MROUND2(-10, -3) ⟶ -9
|
96
|
+
MROUND2(0, 3) ⟶ 0
|
97
|
+
MROUND2(10, 0) ⟶ nil
|
98
|
+
MROUND2(10.5, 0.3) ⟶ 10.5
|
99
|
+
MROUND2(5, 3) ⟶ 6
|
100
|
+
MROUND2(4, 3) ⟶ 3
|
101
|
+
MROUND2(10.5678, 0.05) ⟶ 10.55
|
102
|
+
|
103
|
+
CEILING(7, 1) ⟶ 7
|
104
|
+
CEILING(7) ⟶ 7
|
105
|
+
CEILING(7.05, 1) ⟶ 8
|
106
|
+
CEILING(7.05) ⟶ 8
|
107
|
+
CEILING(7, 5) ⟶ 10
|
108
|
+
CEILING(49.25, 5) ⟶ 50
|
109
|
+
CEILING(501, 100) ⟶ 600
|
110
|
+
CEILING(550, 100) ⟶ 600
|
111
|
+
CEILING(10.1, 0.25) ⟶ 10.25
|
112
|
+
|
113
|
+
FLOOR(7, 5) ⟶ 5
|
114
|
+
FLOOR(49.25) ⟶ 49
|
115
|
+
FLOOR(49.25, 1) ⟶ 49
|
116
|
+
FLOOR(550, 100) ⟶ 500
|
117
|
+
FLOOR(10.1, 0.25) ⟶ 10
|
118
|
+
|
119
|
+
TRUNC(8.9) ⟶ 8
|
120
|
+
TRUNC(8.9, 0) ⟶ 8
|
121
|
+
TRUNC(3.14159, 2) ⟶ 3.14
|
122
|
+
TRUNC(2.678, 1) ⟶ 2.6
|
123
|
+
|
124
|
+
"ab\"cd'ef" ⟶ "ab\"cd'ef"
|
125
|
+
'ab"cd\'ef' ⟶ "ab\"cd'ef"
|
126
|
+
|
127
|
+
0.45 * we_c - 0.15 * it_c + 0.15 * sm_c + 0.25 * gp_c ⟶ \
|
128
|
+
{:we_c=>1.1, :it_c=>2.2, :sm_c=>3.3, :gp_c=>4.4} ⟶ 1.76
|
129
|
+
|
130
|
+
1 + "a" ⟶ "1a"
|
131
|
+
1 + 2 ⟶ 3
|
132
|
+
1 + a.b.c ⟶ {:a=>{:b=>{:c=>66}}} ⟶ 67
|
133
|
+
1 + v0 ⟶ {:v0=>70} ⟶ 71
|
134
|
+
1 - -2 ⟶ 3
|
135
|
+
1 - 2 ⟶ -1
|
136
|
+
1 / 5 ⟶ 0.2
|
137
|
+
"a" & "bc" ⟶ "abc"
|
138
|
+
1 & 1 ⟶ "11"
|
139
|
+
"ab" & c & d & "ef" ⟶ {:c=>"c"} ⟶ "abcef"
|
140
|
+
1 < 2 ⟶ true
|
141
|
+
1 <= 2 ⟶ true
|
142
|
+
1 IN a ⟶ {:a=>[0, 1, 2]} ⟶ true
|
143
|
+
1 IN { 0, 1 } ⟶ true
|
144
|
+
1.0 / 5 ⟶ 0.2
|
145
|
+
2 != 3 ⟶ true
|
146
|
+
2 <= 2 ⟶ true
|
147
|
+
2 >= 2 ⟶ true
|
148
|
+
3 = 3 ⟶ true
|
149
|
+
3 >= 2 ⟶ true
|
150
|
+
|
151
|
+
AND(TRUE()) ⟶ true
|
152
|
+
AND(TRUE(), TRUE()) ⟶ true
|
153
|
+
|
154
|
+
CASE(AND(a > 99, 2 > 1), 2, a > 9, 1, -1) ⟶ {:a=>100} ⟶ 2
|
155
|
+
CASE(a > 99, 2, a > 9, 1, -1) ⟶ {:a=>100} ⟶ 2
|
156
|
+
CASE(eb, "&", 10, "g", 11, "a", 12, 13) ⟶ {:eb=>"nada"} ⟶ 13
|
157
|
+
|
158
|
+
COUNTA(a) ⟶ {:a=>[]} ⟶ 0
|
159
|
+
COUNTA(a) ⟶ {:a=>[1, 2, 3]} ⟶ 3
|
160
|
+
|
161
|
+
HAS(a, "b") ⟶ {:a=>["a", "b", "c"]} ⟶ true
|
162
|
+
HAS(a, 1) ⟶ {:a=>[0, 1, 2]} ⟶ true
|
163
|
+
|
164
|
+
IF(FALSE(), 1, 2) ⟶ 2
|
165
|
+
IF(TRUE(), 1, 2) ⟶ 1
|
166
|
+
IF(f, 1, 2) ⟶ {:f=>false} ⟶ 2
|
167
|
+
IF(t, 1, 2) ⟶ {:t=>true} ⟶ 1
|
168
|
+
|
169
|
+
INDEX(a, -1) ⟶ {:a=>[0, 1, 2, "trois"]} ⟶ "trois"
|
170
|
+
INDEX(a, -2) ⟶ {:a=>[0, 1, 2, "trois"]} ⟶ 2
|
171
|
+
INDEX(a, 1) ⟶ {:a=>[0, 1, 2]} ⟶ 0
|
172
|
+
INDEX(a, 2) ⟶ {:a=>[0, 1, 2]} ⟶ 1
|
173
|
+
INDEX(a, COUNTA(a)) ⟶ {:a=>[0, "two"]} ⟶ "two"
|
174
|
+
INDEX({ 'ab', 'cd', 'ef' }, -2) ⟶ "cd"
|
175
|
+
|
176
|
+
ISBLANK(a) ⟶ {:a=>""} ⟶ true
|
177
|
+
ISBLANK(a) ⟶ {} ⟶ true
|
178
|
+
|
179
|
+
ISNUMBER(123) ⟶ true
|
180
|
+
ISNUMBER(123.12) ⟶ true
|
181
|
+
|
182
|
+
LN(3044.31) ⟶ 8.02
|
183
|
+
LN(a) ⟶ {:a=>[3044.31, 3047.12]} ⟶ [8.02, 8.02]
|
184
|
+
LN({ 3044.31, 3047.12 }) ⟶ [8.02, 8.02]
|
185
|
+
|
186
|
+
MATCH("b", a, 0) ⟶ {:a=>["a", "b", "c"]} ⟶ 1
|
187
|
+
MATCH("d", a, 0) ⟶ {:a=>["a", "b"]} ⟶ -1
|
188
|
+
MATCH(1, a, 0) ⟶ {:a=>[0, 1, 2]} ⟶ 1
|
189
|
+
|
190
|
+
MAX(-1, -2, "a", -3) ⟶ -1
|
191
|
+
MAX(-1, -2, -3) ⟶ -1
|
192
|
+
MAX(1, 2, 3) ⟶ 3
|
193
|
+
MIN(-1, -2, "a", -3) ⟶ -1
|
194
|
+
MIN(-1, -2, -3) ⟶ -3
|
195
|
+
MIN(1, 2, 3) ⟶ 1
|
196
|
+
NOT(FALSE()) ⟶ true
|
197
|
+
|
198
|
+
OR(1 = 2, 2 = 2) ⟶ true
|
199
|
+
OR(TRUE(), FALSE()) ⟶ true
|
200
|
+
|
201
|
+
PRODUCT(2, 3, 4) ⟶ 24
|
202
|
+
PRODUCT({ 2, 3, 4 }) ⟶ 24
|
203
|
+
PRODUCT({ 2, 3, 4 }, 2) ⟶ 48
|
204
|
+
PRODUCT({ 2, 3, 4 }, a, 2) ⟶ {:a=>[0.5, 0.5]} ⟶ 12
|
205
|
+
|
206
|
+
PROPER("alpha bravo charly") ⟶ "Alpha Bravo Charly"
|
207
|
+
|
208
|
+
SORT({ 1, "aa", 7, 2 }, 1, -1) ⟶ ["aa", 7, 2, 1]
|
209
|
+
SORT({ 1, 3, 2 }) ⟶ [1, 2, 3]
|
210
|
+
SORT({ 1, 3, 2 }, 1, -1) ⟶ [3, 2, 1]
|
211
|
+
|
212
|
+
SQRT(260) ⟶ 16.1245
|
213
|
+
SQRT(a) ⟶ {:a=>[260, 81]} ⟶ [16.1245, 9]
|
214
|
+
SQRT({ 260, 81 }) ⟶ [16.1245, 9]
|
215
|
+
|
216
|
+
STDEV(a) ⟶ {:a=>[10, 11]} ⟶ 0.71
|
217
|
+
|
218
|
+
SUM(2, 3, 4) ⟶ 9
|
219
|
+
SUM({ 2, 3, 4 }) ⟶ 9
|
220
|
+
SUM({ 2, 3, 4 }, 2) ⟶ 11
|
221
|
+
SUM({ 2, 3, 4 }, a, 2) ⟶ {:a=>[0.5, 0.5]} ⟶ 12
|
222
|
+
|
223
|
+
TRUE() ⟶ true
|
224
|
+
|
225
|
+
UNIQUE(a) ⟶ {:a=>[1, 2, 1, 1, 2, 3]} ⟶ [1, 2, 3]
|
226
|
+
UNIQUE({ 1, 1 }) ⟶ [1]
|
227
|
+
|
228
|
+
UPPER("alpha bravo charly") ⟶ "ALPHA BRAVO CHARLY"
|
229
|
+
LOWER("ALPHA BRAVO Charly") ⟶ "alpha bravo charly"
|
230
|
+
|
231
|
+
a != "" ⟶ {:a=>"abc"} ⟶ true
|
232
|
+
|
233
|
+
LAMBDA(a, b, a + b) ⟶ {}
|
234
|
+
|
235
|
+
KALL(LAMBDA(a, b, a + b), 7, -3) ⟶ 4
|
236
|
+
KALL(LAMBDA(a, b, a + b), 7, -2, 1) ⟶ 5
|
237
|
+
|
238
|
+
MAP({ 2, 3, 4 }, LAMBDA(a, 2 * a)) ⟶ [4, 6, 8]
|
239
|
+
|
240
|
+
REDUCE(0, { 2, 3, 4 }, LAMBDA(a, e, a + e)) ⟶ 9
|
241
|
+
REDUCE({ 2, 3, 5 }, LAMBDA(a, e, a + e)) ⟶ 10
|
242
|
+
|
243
|
+
ORV('', '', 1) ⟶ 1
|
244
|
+
ORV('', b, a, 3) ⟶ {:a=>2} ⟶ 2
|
245
|
+
|
246
|
+
TEXTJOIN("/", TRUE(), "a", "b") ⟶ "a/b"
|
247
|
+
TEXTJOIN(", ", TRUE(), a, "zz") ⟶ {:a=>["ab", "cd", "ef1"]} ⟶ "ab, cd, ef1, zz"
|
248
|
+
TEXTJOIN(", ", TRUE(), a, "zz") ⟶ {:a=>["ab", "", "ef1"]} ⟶ "ab, ef1, zz"
|
249
|
+
TEXTJOIN(", ", FALSE(), a, "zz") ⟶ {:a=>["ab", "", "ef1"]} ⟶ "ab, , ef1, zz"
|
250
|
+
|
251
|
+
D() ⟶ \
|
252
|
+
{}
|
253
|
+
D('alpha') ⟶ \
|
254
|
+
{ alpha: nil }
|
255
|
+
D('alpha', 'bravo') ⟶ \
|
256
|
+
{ alpha: 'bravo' }
|
257
|
+
D('a', 1, 'b', 'deux', 'charly', { 1, 'deux' }) ⟶ \
|
258
|
+
{ a: 1, b: 'deux', charly: [ 1, 'deux' ] }
|
259
|
+
```
|
260
|
+
|
261
|
+
|
262
|
+
## LICENSE
|
263
|
+
|
264
|
+
MIT, see [LICENSE.txt](LICENSE.txt)
|
265
|
+
|
@@ -0,0 +1,530 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Xel
|
5
|
+
|
6
|
+
# eval_XXX
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def _eval_args(tree, context, opts={})
|
13
|
+
|
14
|
+
sta = opts[:start] || 1
|
15
|
+
max = opts[:max] || 99
|
16
|
+
|
17
|
+
a = []
|
18
|
+
tree[sta..-1].each do |t|
|
19
|
+
break if a.length >= max
|
20
|
+
a << do_eval(t, context)
|
21
|
+
end
|
22
|
+
|
23
|
+
a
|
24
|
+
end
|
25
|
+
|
26
|
+
def eval_str(tree, context); tree[1]; end
|
27
|
+
|
28
|
+
def eval_num(tree, context)
|
29
|
+
|
30
|
+
s = tree[1].gsub(',', '')
|
31
|
+
|
32
|
+
s.index('.') ? s.to_f : s.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def eval_var(tree, context)
|
36
|
+
|
37
|
+
tree[1].split('.')
|
38
|
+
.inject(context) { |r, k|
|
39
|
+
if r && r.respond_to?(:has_key?)
|
40
|
+
ks = k.to_sym
|
41
|
+
if r.has_key?(k)
|
42
|
+
r[k]
|
43
|
+
elsif r.has_key?(ks)
|
44
|
+
r[ks]
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end }
|
51
|
+
end
|
52
|
+
|
53
|
+
def eval_inv(tree, context)
|
54
|
+
1.0 / do_eval(tree[1], context)
|
55
|
+
end
|
56
|
+
def eval_opp(tree, context)
|
57
|
+
- do_eval(tree[1], context)
|
58
|
+
end
|
59
|
+
|
60
|
+
def eval_bool(tree, context); tree[0].downcase == 'true'; end
|
61
|
+
|
62
|
+
def do_eval_equal(sign, a0, a1)
|
63
|
+
|
64
|
+
a0 = '' if a0 == nil
|
65
|
+
a1 = '' if a1 == nil
|
66
|
+
|
67
|
+
sign == '=' ? a0 == a1 : a0 != a1
|
68
|
+
end
|
69
|
+
|
70
|
+
def eval_cmp(tree, context)
|
71
|
+
|
72
|
+
args = _eval_args(tree, context, start: 2)
|
73
|
+
|
74
|
+
case tree[1]
|
75
|
+
when '=', '!=' then do_eval_equal(tree[1], args[0], args[1])
|
76
|
+
when '>' then args[0] > args[1]
|
77
|
+
when '<' then args[0] < args[1]
|
78
|
+
when '>=' then args[0] >= args[1]
|
79
|
+
when '<=' then args[0] <= args[1]
|
80
|
+
when '=~' then !! args[0].to_s.match(args[1].to_s)
|
81
|
+
when 'IN' then args[1].include?(args[0])
|
82
|
+
else false
|
83
|
+
end
|
84
|
+
|
85
|
+
rescue
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def eval_TRUE(tree, context); tree[0] == 'TRUE'; end
|
90
|
+
alias eval_FALSE eval_TRUE
|
91
|
+
|
92
|
+
alias eval_arr _eval_args
|
93
|
+
|
94
|
+
def eval_plus(tree, context)
|
95
|
+
|
96
|
+
args = _eval_args(tree, context)
|
97
|
+
|
98
|
+
if args[0].is_a?(Array)
|
99
|
+
args.inject([]) { |a, arg| a.concat(arg) }
|
100
|
+
elsif args.find { |a| ! a.is_a?(Numeric) }
|
101
|
+
args.map(&:to_s).join
|
102
|
+
elsif args.all? { |a| a.is_a?(Numeric) }
|
103
|
+
args.inject(&:+)
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def eval_amp(tree, context)
|
110
|
+
|
111
|
+
_eval_args(tree, context, start: 1).collect(&:to_s).join
|
112
|
+
end
|
113
|
+
|
114
|
+
def eval_AND(tree, context)
|
115
|
+
|
116
|
+
! tree[1..-1].find { |c| do_eval(c, context) != true }
|
117
|
+
end
|
118
|
+
|
119
|
+
def eval_OR(tree, context)
|
120
|
+
|
121
|
+
!! tree[1..-1].find { |c| do_eval(c, context) == true }
|
122
|
+
end
|
123
|
+
|
124
|
+
def eval_NOT(tree, context)
|
125
|
+
|
126
|
+
! do_eval(tree[1], context)
|
127
|
+
end
|
128
|
+
|
129
|
+
def eval_ORV(tree, context)
|
130
|
+
|
131
|
+
tree[1..-1].each do |t|
|
132
|
+
v = do_eval(t, context)
|
133
|
+
return v if v != '' && v!= nil
|
134
|
+
end
|
135
|
+
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def eval_IF(tree, context)
|
140
|
+
|
141
|
+
return do_eval(tree[2], context) if do_eval(tree[1], context)
|
142
|
+
do_eval(tree[3], context)
|
143
|
+
end
|
144
|
+
|
145
|
+
def eval_CASE(tree, context)
|
146
|
+
|
147
|
+
control = do_eval(tree[1], context)
|
148
|
+
args = tree[2..-1]
|
149
|
+
|
150
|
+
if control == true || control == false
|
151
|
+
args.unshift(control)
|
152
|
+
control = true
|
153
|
+
end
|
154
|
+
|
155
|
+
default = args.size.odd? ? args.pop : nil
|
156
|
+
|
157
|
+
while (ab = args.shift(2)).any?
|
158
|
+
return do_eval(ab[1], context) if control == do_eval(ab[0], context)
|
159
|
+
end
|
160
|
+
do_eval(default, context)
|
161
|
+
end
|
162
|
+
alias eval_SWITCH eval_CASE
|
163
|
+
|
164
|
+
def eval_UNIQUE(tree, context)
|
165
|
+
|
166
|
+
arr = do_eval(tree[1], context)
|
167
|
+
|
168
|
+
fail ArgumentError.new("UNIQUE() expects array not #{arr.class}") \
|
169
|
+
unless arr.is_a?(Array)
|
170
|
+
|
171
|
+
arr.uniq
|
172
|
+
end
|
173
|
+
|
174
|
+
# SORT({ 1, 3, 2 }) --> [ 1, 2, 3 ]
|
175
|
+
# SORT({ 1, 3, 2 }, 1, -1) --> [ 3, 2, 1 ]
|
176
|
+
#
|
177
|
+
def eval_SORT(tree, context)
|
178
|
+
|
179
|
+
#arr, col, dir = _eval_args(tree, context, max: 3)
|
180
|
+
arr, _, dir = _eval_args(tree, context, max: 3)
|
181
|
+
|
182
|
+
fail ArgumentError.new("SORT() expects array not #{arr.class}") \
|
183
|
+
unless arr.is_a?(Array)
|
184
|
+
|
185
|
+
r =
|
186
|
+
arr.all? { |e| e.is_a?(Numeric) } ? arr.sort :
|
187
|
+
arr.sort_by(&:to_s)
|
188
|
+
|
189
|
+
dir == -1 ? r.reverse : r
|
190
|
+
end
|
191
|
+
|
192
|
+
def eval_MUL(tree, context)
|
193
|
+
|
194
|
+
args = _eval_args(tree, context)
|
195
|
+
|
196
|
+
if args.find { |a| ! (a.is_a?(Integer) || a.is_a?(Float)) }
|
197
|
+
fail ArgumentError.new("cannot multiply #{args.inspect}")
|
198
|
+
end
|
199
|
+
|
200
|
+
args.reduce(&:*)
|
201
|
+
end
|
202
|
+
|
203
|
+
def eval_SUM(tree, context)
|
204
|
+
|
205
|
+
f = lambda { |r, e|
|
206
|
+
case e
|
207
|
+
when Numeric then r + e
|
208
|
+
when Array then e.inject(r, &f)
|
209
|
+
else r; end }
|
210
|
+
|
211
|
+
_eval_args(tree, context).inject(0, &f);
|
212
|
+
end
|
213
|
+
|
214
|
+
def eval_PRODUCT(tree, context)
|
215
|
+
|
216
|
+
f = lambda { |r, e|
|
217
|
+
case e
|
218
|
+
when Numeric then r * e
|
219
|
+
when Array then e.inject(r, &f)
|
220
|
+
else r; end }
|
221
|
+
|
222
|
+
_eval_args(tree, context).inject(1, &f)
|
223
|
+
end
|
224
|
+
|
225
|
+
def eval_MIN(tree, context)
|
226
|
+
|
227
|
+
args = _eval_args(tree, context)
|
228
|
+
|
229
|
+
if args.find { |a| ! (a.is_a?(Integer) || a.is_a?(Float)) }
|
230
|
+
args.first
|
231
|
+
else
|
232
|
+
args.min
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def eval_MAX(tree, context)
|
237
|
+
|
238
|
+
args = _eval_args(tree, context)
|
239
|
+
|
240
|
+
if args.find { |a| ! (a.is_a?(Integer) || a.is_a?(Float)) }
|
241
|
+
args.first
|
242
|
+
else
|
243
|
+
args.max
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def eval_MATCH(tree, context)
|
248
|
+
|
249
|
+
elt = do_eval(tree[1], context)
|
250
|
+
arr = do_eval(tree[2], context)
|
251
|
+
|
252
|
+
return -1 unless arr.is_a?(Array)
|
253
|
+
arr.index(elt) || -1
|
254
|
+
end
|
255
|
+
|
256
|
+
def eval_HAS(tree, context)
|
257
|
+
|
258
|
+
col = do_eval(tree[1], context)
|
259
|
+
elt = do_eval(tree[2], context)
|
260
|
+
|
261
|
+
return !! col.index(elt) if col.is_a?(Array)
|
262
|
+
return col.has_key?(elt) if col.is_a?(Hash)
|
263
|
+
false
|
264
|
+
end
|
265
|
+
|
266
|
+
def eval_INDEX(tree, context)
|
267
|
+
|
268
|
+
col = do_eval(tree[1], context)
|
269
|
+
i = do_eval(tree[2], context)
|
270
|
+
|
271
|
+
return 0 unless col.is_a?(Array)
|
272
|
+
return 0 unless i.is_a?(Numeric)
|
273
|
+
|
274
|
+
i < 0 ?
|
275
|
+
col[i] :
|
276
|
+
col[i.to_i - 1]
|
277
|
+
end
|
278
|
+
|
279
|
+
def eval_COUNTA(tree, context)
|
280
|
+
|
281
|
+
col = do_eval(tree[1], context)
|
282
|
+
|
283
|
+
col.is_a?(Array) ? col.length : 0
|
284
|
+
end
|
285
|
+
|
286
|
+
def eval_ISBLANK(tree, context)
|
287
|
+
|
288
|
+
val = do_eval(tree[1], context)
|
289
|
+
|
290
|
+
val == '' || val == nil
|
291
|
+
end
|
292
|
+
|
293
|
+
def eval_ISNUMBER(tree, context)
|
294
|
+
|
295
|
+
do_eval(tree[1], context).is_a?(Numeric)
|
296
|
+
end
|
297
|
+
|
298
|
+
def eval_PROPER(tree, context)
|
299
|
+
do_eval(tree[1], context).gsub(/(^|[^a-z])([a-z])/) { $1 + $2.upcase }
|
300
|
+
end
|
301
|
+
def eval_LOWER(tree, context)
|
302
|
+
do_eval(tree[1], context).downcase
|
303
|
+
end
|
304
|
+
def eval_UPPER(tree, context)
|
305
|
+
do_eval(tree[1], context).upcase
|
306
|
+
end
|
307
|
+
|
308
|
+
def eval_LN(tree, context)
|
309
|
+
a = do_eval(tree[1], context)
|
310
|
+
return a.map { |e| Math.log(e) } if a.is_a?(Array)
|
311
|
+
Math.log(a)
|
312
|
+
end
|
313
|
+
def eval_SQRT(tree, context)
|
314
|
+
a = do_eval(tree[1], context)
|
315
|
+
return a.map { |e| Math.sqrt(e) } if a.is_a?(Array)
|
316
|
+
Math.sqrt(a)
|
317
|
+
end
|
318
|
+
|
319
|
+
def eval_LET(tree, context)
|
320
|
+
|
321
|
+
ctx = context.dup
|
322
|
+
|
323
|
+
key = nil
|
324
|
+
#
|
325
|
+
tree[1..-2].each_with_index do |t, i|
|
326
|
+
if i % 2 == 0
|
327
|
+
key = (t[0] == 'var') ? t[1] : do_eval(t, ctx).to_s
|
328
|
+
else
|
329
|
+
ctx[key] = do_eval(t, ctx)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
do_eval(tree[-1], ctx)
|
334
|
+
end
|
335
|
+
|
336
|
+
def eval_ROUND(tree, context)
|
337
|
+
|
338
|
+
args = _eval_args(tree, context, max: 2)
|
339
|
+
args << 0 if args.length < 2
|
340
|
+
|
341
|
+
args[0].round(args[1])
|
342
|
+
end
|
343
|
+
|
344
|
+
def eval_MROUND(tree, context)
|
345
|
+
|
346
|
+
n, m = _eval_args(tree, context, max: 2)
|
347
|
+
|
348
|
+
return Float::NAN if (n * m) < 0
|
349
|
+
|
350
|
+
(n.to_f / m).round * m rescue nil
|
351
|
+
end
|
352
|
+
|
353
|
+
alias eval_MROUND2 eval_MROUND
|
354
|
+
|
355
|
+
def eval_CEILING(tree, context)
|
356
|
+
|
357
|
+
as = _eval_args(tree, context, max: 2)
|
358
|
+
as << 1 if as.length < 2
|
359
|
+
n, m = as
|
360
|
+
r = n % m
|
361
|
+
|
362
|
+
r == 0 ? n : n - r + m
|
363
|
+
end
|
364
|
+
|
365
|
+
def eval_FLOOR(tree, context)
|
366
|
+
|
367
|
+
as = _eval_args(tree, context, max: 2)
|
368
|
+
as << 1 if as.length < 2
|
369
|
+
n, m = as
|
370
|
+
|
371
|
+
n - (n % m)
|
372
|
+
end
|
373
|
+
|
374
|
+
def eval_TRUNC(tree, context)
|
375
|
+
|
376
|
+
as = _eval_args(tree, context, max: 2)
|
377
|
+
as << 0 if as.length < 2
|
378
|
+
n = as[0]; m = 10 ** as[1]
|
379
|
+
|
380
|
+
(n * m).floor.to_f / m
|
381
|
+
end
|
382
|
+
|
383
|
+
def p2(n); n * n; end
|
384
|
+
|
385
|
+
def eval_STDEV(tree, context)
|
386
|
+
|
387
|
+
a = do_eval(tree[1], context)
|
388
|
+
s = a.inject(0.0) { |acc, e| acc + e }
|
389
|
+
m = s / a.length
|
390
|
+
s = a.inject(0.0) { |acc, e| acc + p2(e - m) }
|
391
|
+
v = s / (a.length - 1)
|
392
|
+
|
393
|
+
Math.sqrt(v)
|
394
|
+
end
|
395
|
+
|
396
|
+
def eval_VLOOKUP(tree, context)
|
397
|
+
|
398
|
+
k, t, i = _eval_args(tree, context, max: 3)
|
399
|
+
|
400
|
+
fail ArgumentError.new(
|
401
|
+
"VLOOKUP() arg 3 #{tree[3].inspect} is not an integer"
|
402
|
+
) unless i.is_a?(Integer)
|
403
|
+
|
404
|
+
fail ArgumentError.new(
|
405
|
+
"VLOOKUP() arg 2 #{tree[2].inspect} doesn't point to an array of arrays"
|
406
|
+
) unless t.is_a?(Array)
|
407
|
+
|
408
|
+
t.each_with_index do |r, j|
|
409
|
+
|
410
|
+
fail ArgumentError.new(
|
411
|
+
"VLOOKUP() arg2 row #{j + 1} of table is not an array"
|
412
|
+
) unless r.is_a?(Array)
|
413
|
+
|
414
|
+
return r[i - 1] if r[0] == k
|
415
|
+
end
|
416
|
+
|
417
|
+
nil
|
418
|
+
end
|
419
|
+
|
420
|
+
def eval_LAMBDA(tree, context)
|
421
|
+
|
422
|
+
args = tree[1..-1].collect { |t| t[1] }
|
423
|
+
code = tree[-1]
|
424
|
+
|
425
|
+
l =
|
426
|
+
Proc.new do |*argl|
|
427
|
+
ctx = context.dup.merge(argl.pop)
|
428
|
+
args.each_with_index { |arg, i| ctx[arg] = argl[i] }
|
429
|
+
Xel.do_eval(code, ctx)
|
430
|
+
end
|
431
|
+
class << l; attr_accessor :_source; end
|
432
|
+
l._source = tree._source
|
433
|
+
|
434
|
+
l
|
435
|
+
end
|
436
|
+
|
437
|
+
def eval_KALL(tree, context)
|
438
|
+
|
439
|
+
args = _eval_args(tree, context)
|
440
|
+
args << context
|
441
|
+
|
442
|
+
fun = args.shift
|
443
|
+
|
444
|
+
fun.call(*args)
|
445
|
+
end
|
446
|
+
|
447
|
+
def eval_MAP(tree, context)
|
448
|
+
|
449
|
+
arr, fun = _eval_args(tree, context, max: 2)
|
450
|
+
|
451
|
+
arr.collect { |e| fun.call(e, context) }
|
452
|
+
end
|
453
|
+
|
454
|
+
def eval_REDUCE(tree, context)
|
455
|
+
|
456
|
+
ts = tree[1..-1]
|
457
|
+
fun = do_eval(ts.pop, context)
|
458
|
+
v0 = do_eval(ts[0], context)
|
459
|
+
|
460
|
+
acc, arr = nil
|
461
|
+
if ts.length == 1
|
462
|
+
arr = v0
|
463
|
+
acc = arr.shift
|
464
|
+
else
|
465
|
+
acc = v0
|
466
|
+
arr = do_eval(ts[1], context)
|
467
|
+
end
|
468
|
+
|
469
|
+
arr.inject(acc) { |r, e| fun.call(r, e, context) }
|
470
|
+
end
|
471
|
+
|
472
|
+
def eval_TEXTJOIN(tree, context)
|
473
|
+
|
474
|
+
agg = lambda { |acc, x|
|
475
|
+
case x
|
476
|
+
when String then acc << x.strip
|
477
|
+
when Array then x.each { |e| agg.call(acc, e) }
|
478
|
+
when nil then acc << ''
|
479
|
+
else acc << x.inspect; end
|
480
|
+
acc }
|
481
|
+
|
482
|
+
del, ign = _eval_args(tree, context, max: 2)
|
483
|
+
|
484
|
+
txs = tree[3..-1].inject([]) { |r, t| agg.call(r, do_eval(t, context)) }
|
485
|
+
|
486
|
+
txs = txs.select { |t| t.length > 0 } if ign
|
487
|
+
|
488
|
+
txs.join(del)
|
489
|
+
end
|
490
|
+
|
491
|
+
def eval_D(tree, context)
|
492
|
+
|
493
|
+
_eval_args(tree, context)
|
494
|
+
.each_slice(2)
|
495
|
+
.inject({}) { |h, (k, v)| h[k] = v; h }
|
496
|
+
end
|
497
|
+
|
498
|
+
def do_eval(tree, context={})
|
499
|
+
|
500
|
+
return tree unless tree.is_a?(Array) && tree.first.class == String
|
501
|
+
|
502
|
+
t0 = tree[0]
|
503
|
+
|
504
|
+
if (v = context[t0]) && v.is_a?(Proc)
|
505
|
+
args = _eval_args(tree, context)
|
506
|
+
args << context
|
507
|
+
return v.call(*args)
|
508
|
+
end
|
509
|
+
|
510
|
+
cfs = context['_custom_functions'] || context[:_custom_functions]
|
511
|
+
|
512
|
+
if (v = cfs && (cfs[t0] || cfs[t0.to_sym])) && v.is_a?(Proc)
|
513
|
+
return v.call(tree, context)
|
514
|
+
end
|
515
|
+
|
516
|
+
send("eval_#{t0}", tree, context)
|
517
|
+
end
|
518
|
+
|
519
|
+
public
|
520
|
+
|
521
|
+
def eval(s, context={})
|
522
|
+
|
523
|
+
t = s.is_a?(Array) ? s : Xel::Parser.parse(s)
|
524
|
+
fail ArgumentError.new("syntax error in >>#{s}<<") unless t
|
525
|
+
|
526
|
+
do_eval(t, context)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
data/lib/xel/parser.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Xel::Parser include Raabro
|
5
|
+
|
6
|
+
# parse
|
7
|
+
|
8
|
+
def aa(i); rex(nil, i, /\{\s*/); end
|
9
|
+
def az(i); rex(nil, i, /\}\s*/); end
|
10
|
+
def pa(i); rex(nil, i, /\(\s*/); end
|
11
|
+
def pz(i); rex(nil, i, /\)\s*/); end
|
12
|
+
def com(i); rex(nil, i, /,\s*/); end
|
13
|
+
|
14
|
+
def number(i)
|
15
|
+
rex(:number, i, /-?(\.[0-9]+|([0-9][,0-9]*[0-9]|[0-9]+)(\.[0-9]+)?)\s*/)
|
16
|
+
end
|
17
|
+
|
18
|
+
def var(i); rex(:var, i, /[a-z_][A-Za-z0-9_.]*\s*/); end
|
19
|
+
|
20
|
+
def arr(i); eseq(:arr, i, :aa, :cmp, :com, :az); end
|
21
|
+
|
22
|
+
def qstring(i); rex(:qstring, i, /'(\\'|[^'])*'\s*/); end
|
23
|
+
def dqstring(i); rex(:dqstring, i, /"(\\"|[^"])*"\s*/); end
|
24
|
+
def string(i); alt(:string, i, :dqstring, :qstring); end
|
25
|
+
|
26
|
+
def funargs(i); eseq(:funargs, i, :pa, :cmp, :com, :pz); end
|
27
|
+
def funname(i); rex(:funname, i, /[_a-zA-Z][_a-zA-Z0-9]*/); end
|
28
|
+
def fun(i); seq(:fun, i, :funname, :funargs); end
|
29
|
+
|
30
|
+
def comparator(i); rex(:comparator, i, /([\<\>]=?|=~|!?=|IN)\s*/); end
|
31
|
+
def multiplier(i); rex(:multiplier, i, /[*\/]\s*/); end
|
32
|
+
def adder(i); rex(:adder, i, /[+\-&]\s*/); end
|
33
|
+
|
34
|
+
def par(i); seq(:par, i, :pa, :cmp, :pz); end
|
35
|
+
def exp(i); alt(:exp, i, :par, :fun, :number, :string, :arr, :var); end
|
36
|
+
|
37
|
+
def mul(i); jseq(:mul, i, :exp, :multiplier); end
|
38
|
+
def add(i); jseq(:add, i, :mul, :adder); end
|
39
|
+
|
40
|
+
def rcmp(i); seq(:rcmp, i, :comparator, :add); end
|
41
|
+
def cmp(i); seq(:cmp, i, :add, :rcmp, '?'); end
|
42
|
+
|
43
|
+
def prequal(i); rex(nil, i, /\s*=?\s*/); end
|
44
|
+
def root(i); seq(nil, i, :prequal, :cmp); end
|
45
|
+
|
46
|
+
# rewrite
|
47
|
+
|
48
|
+
def rewrite_cmp(tree)
|
49
|
+
|
50
|
+
return rewrite(tree.children.first) if tree.children.size == 1
|
51
|
+
|
52
|
+
[ 'cmp',
|
53
|
+
tree.children[1].children.first.string.strip,
|
54
|
+
rewrite(tree.children[0]),
|
55
|
+
rewrite(tree.children[1].children[1]) ]
|
56
|
+
end
|
57
|
+
|
58
|
+
def rewrite_add(tree)
|
59
|
+
|
60
|
+
return rewrite(tree.children.first) if tree.children.size == 1
|
61
|
+
|
62
|
+
cn = tree.children.dup
|
63
|
+
a = [ tree.name == :add ? 'plus' : 'MUL' ]
|
64
|
+
a = [ 'amp' ] if cn[1] && cn[1].strinp == '&'
|
65
|
+
mod = nil
|
66
|
+
|
67
|
+
while c = cn.shift
|
68
|
+
v = rewrite(c)
|
69
|
+
v = [ mod, v ] if mod
|
70
|
+
a << v
|
71
|
+
c = cn.shift
|
72
|
+
break unless c
|
73
|
+
mod = { '-' => 'opp', '/' => 'inv' }[c.string.strip]
|
74
|
+
end
|
75
|
+
|
76
|
+
a
|
77
|
+
end
|
78
|
+
alias rewrite_mul rewrite_add
|
79
|
+
|
80
|
+
def rewrite_fun(tree)
|
81
|
+
|
82
|
+
t =
|
83
|
+
[ tree.children[0].string ] +
|
84
|
+
tree.children[1].children.select(&:name).collect { |c| rewrite(c) }
|
85
|
+
class << t; attr_accessor :_source; end
|
86
|
+
t._source = tree.strinp
|
87
|
+
|
88
|
+
t
|
89
|
+
end
|
90
|
+
|
91
|
+
def rewrite_exp(tree); rewrite(tree.children[0]); end
|
92
|
+
def rewrite_par(tree); rewrite(tree.children[1]); end
|
93
|
+
|
94
|
+
def rewrite_arr(tree)
|
95
|
+
|
96
|
+
[ 'arr',
|
97
|
+
*tree.children.inject([]) { |a, c| a << rewrite(c) if c.name; a } ]
|
98
|
+
end
|
99
|
+
|
100
|
+
def rewrite_var(tree); [ 'var', tree.string.strip ]; end
|
101
|
+
def rewrite_number(tree); [ 'num', tree.string.strip ]; end
|
102
|
+
|
103
|
+
def rewrite_string(tree)
|
104
|
+
|
105
|
+
s = tree.children[0].string.strip
|
106
|
+
q = s[0]
|
107
|
+
s = s[1..-2]
|
108
|
+
|
109
|
+
[ 'str', q == '"' ? s.gsub("\\\"", '"') : s.gsub("\\'", "'") ]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
data/lib/xel.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Xel
|
5
|
+
|
6
|
+
VERSION = '1.5.1'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'raabro'
|
10
|
+
|
11
|
+
require 'xel/parser'
|
12
|
+
require 'xel/evaluator'
|
13
|
+
|
14
|
+
|
15
|
+
module Xel
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def parse(s)
|
20
|
+
|
21
|
+
Xel::Parser.parse(s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/xel.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
|
4
|
+
s.name = 'xel'
|
5
|
+
|
6
|
+
s.version = File.read(
|
7
|
+
File.expand_path('../lib/xel.rb', __FILE__)
|
8
|
+
).match(/ VERSION *= *['"]([^'"]+)/)[1]
|
9
|
+
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = [ 'John Mettraux' ]
|
12
|
+
s.email = [ 'jmettraux@gmail.com' ]
|
13
|
+
s.homepage = 'https://github.com/jmettraux/xel'
|
14
|
+
s.license = 'MIT'
|
15
|
+
s.summary = 'interprets the expressions usually found in a spreadsheet cell'
|
16
|
+
|
17
|
+
s.description = %{
|
18
|
+
Xel interprets the expressions usually found in a spreadsheet cell, hence its diminutive name.
|
19
|
+
}.strip
|
20
|
+
|
21
|
+
s.metadata = {
|
22
|
+
'changelog_uri' => s.homepage + '/blob/master/CHANGELOG.md',
|
23
|
+
'documentation_uri' => s.homepage,
|
24
|
+
'bug_tracker_uri' => s.homepage + '/issues',
|
25
|
+
#'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/floraison',
|
26
|
+
'homepage_uri' => s.homepage,
|
27
|
+
'source_code_uri' => s.homepage,
|
28
|
+
#'wiki_uri' => s.homepage + '/wiki',
|
29
|
+
}
|
30
|
+
|
31
|
+
#s.files = `git ls-files`.split("\n")
|
32
|
+
s.files = Dir[
|
33
|
+
'README.{md,txt}',
|
34
|
+
'CHANGELOG.{md,txt}', 'CREDITS.{md,txt}', 'LICENSE.{md,txt}',
|
35
|
+
#'Makefile',
|
36
|
+
'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb',
|
37
|
+
"#{s.name}.gemspec",
|
38
|
+
]
|
39
|
+
|
40
|
+
s.add_runtime_dependency 'raabro', '~> 1.4'
|
41
|
+
|
42
|
+
s.add_development_dependency 'rspec', '~> 3.8'
|
43
|
+
|
44
|
+
s.require_path = 'lib'
|
45
|
+
end
|
46
|
+
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Mettraux
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-07-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: raabro
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.8'
|
41
|
+
description: Xel interprets the expressions usually found in a spreadsheet cell, hence
|
42
|
+
its diminutive name.
|
43
|
+
email:
|
44
|
+
- jmettraux@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- CHANGELOG.md
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- lib/xel.rb
|
53
|
+
- lib/xel/evaluator.rb
|
54
|
+
- lib/xel/parser.rb
|
55
|
+
- xel.gemspec
|
56
|
+
homepage: https://github.com/jmettraux/xel
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata:
|
60
|
+
changelog_uri: https://github.com/jmettraux/xel/blob/master/CHANGELOG.md
|
61
|
+
documentation_uri: https://github.com/jmettraux/xel
|
62
|
+
bug_tracker_uri: https://github.com/jmettraux/xel/issues
|
63
|
+
homepage_uri: https://github.com/jmettraux/xel
|
64
|
+
source_code_uri: https://github.com/jmettraux/xel
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubygems_version: 3.4.10
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: interprets the expressions usually found in a spreadsheet cell
|
84
|
+
test_files: []
|