xel 1.5.1
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/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: []
|