code-ruby 3.1.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/code +97 -20
  4. data/lib/code/concerns/shared.rb +331 -15
  5. data/lib/code/format.rb +22 -2
  6. data/lib/code/network.rb +87 -0
  7. data/lib/code/node/call.rb +79 -2
  8. data/lib/code/node/call_argument.rb +14 -0
  9. data/lib/code/node/code.rb +5 -4
  10. data/lib/code/node/function_parameter.rb +7 -4
  11. data/lib/code/node/list.rb +29 -1
  12. data/lib/code/node/while.rb +21 -0
  13. data/lib/code/object/base_64.rb +132 -6
  14. data/lib/code/object/boolean.rb +60 -0
  15. data/lib/code/object/class.rb +138 -2
  16. data/lib/code/object/code.rb +111 -3
  17. data/lib/code/object/context.rb +57 -1
  18. data/lib/code/object/cryptography.rb +63 -0
  19. data/lib/code/object/date.rb +13339 -462
  20. data/lib/code/object/decimal.rb +1725 -0
  21. data/lib/code/object/dictionary.rb +1835 -12
  22. data/lib/code/object/duration.rb +28 -0
  23. data/lib/code/object/function.rb +261 -23
  24. data/lib/code/object/global.rb +534 -1
  25. data/lib/code/object/html.rb +227 -16
  26. data/lib/code/object/http.rb +244 -14
  27. data/lib/code/object/ics.rb +75 -13
  28. data/lib/code/object/identifier_list.rb +17 -2
  29. data/lib/code/object/integer.rb +1941 -2
  30. data/lib/code/object/json.rb +75 -1
  31. data/lib/code/object/list.rb +3417 -10
  32. data/lib/code/object/nothing.rb +53 -0
  33. data/lib/code/object/number.rb +110 -0
  34. data/lib/code/object/parameter.rb +140 -0
  35. data/lib/code/object/range.rb +596 -14
  36. data/lib/code/object/smtp.rb +95 -12
  37. data/lib/code/object/string.rb +944 -3
  38. data/lib/code/object/super.rb +10 -1
  39. data/lib/code/object/time.rb +13358 -498
  40. data/lib/code/object/url.rb +65 -0
  41. data/lib/code/object.rb +543 -0
  42. data/lib/code/parser.rb +177 -26
  43. data/lib/code-ruby.rb +3 -0
  44. data/lib/code.rb +30 -3
  45. metadata +135 -84
  46. data/.github/dependabot.yml +0 -15
  47. data/.github/workflows/ci.yml +0 -38
  48. data/.gitignore +0 -30
  49. data/.node-version +0 -1
  50. data/.npm-version +0 -1
  51. data/.prettierignore +0 -2
  52. data/.rspec +0 -1
  53. data/.rubocop.yml +0 -140
  54. data/.ruby-version +0 -1
  55. data/.tool-versions +0 -3
  56. data/AGENTS.md +0 -43
  57. data/Gemfile +0 -22
  58. data/Gemfile.lock +0 -292
  59. data/Rakefile +0 -5
  60. data/bin/bundle +0 -123
  61. data/bin/bundle-audit +0 -31
  62. data/bin/bundler-audit +0 -31
  63. data/bin/dorian +0 -31
  64. data/bin/rspec +0 -31
  65. data/bin/rubocop +0 -31
  66. data/bin/test +0 -5
  67. data/code-ruby.gemspec +0 -34
  68. data/docs/precedence.txt +0 -36
  69. data/package-lock.json +0 -14
  70. data/package.json +0 -7
  71. data/spec/bin/code_spec.rb +0 -48
  72. data/spec/code/format_spec.rb +0 -153
  73. data/spec/code/node/call_spec.rb +0 -11
  74. data/spec/code/object/boolean_spec.rb +0 -18
  75. data/spec/code/object/cryptography_spec.rb +0 -25
  76. data/spec/code/object/decimal_spec.rb +0 -50
  77. data/spec/code/object/dictionary_spec.rb +0 -98
  78. data/spec/code/object/function_spec.rb +0 -268
  79. data/spec/code/object/http_spec.rb +0 -33
  80. data/spec/code/object/ics_spec.rb +0 -50
  81. data/spec/code/object/integer_spec.rb +0 -42
  82. data/spec/code/object/list_spec.rb +0 -22
  83. data/spec/code/object/nothing_spec.rb +0 -14
  84. data/spec/code/object/range_spec.rb +0 -23
  85. data/spec/code/object/string_spec.rb +0 -26
  86. data/spec/code/parser/boolean_spec.rb +0 -11
  87. data/spec/code/parser/chained_call_spec.rb +0 -16
  88. data/spec/code/parser/dictionary_spec.rb +0 -18
  89. data/spec/code/parser/function_spec.rb +0 -16
  90. data/spec/code/parser/group_spec.rb +0 -11
  91. data/spec/code/parser/if_modifier_spec.rb +0 -18
  92. data/spec/code/parser/list_spec.rb +0 -17
  93. data/spec/code/parser/number_spec.rb +0 -11
  94. data/spec/code/parser/string_spec.rb +0 -20
  95. data/spec/code/parser_spec.rb +0 -52
  96. data/spec/code/type_spec.rb +0 -21
  97. data/spec/code_spec.rb +0 -642
  98. data/spec/spec_helper.rb +0 -21
  99. data/spec/zeitwerk/loader_spec.rb +0 -7
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- RSpec.describe Code::Type do
6
- describe "valid" do
7
- ["1 + 2", "2 * 3.4"].each do |input|
8
- it input do
9
- expect { Code.evaluate(input) }.not_to raise_error
10
- end
11
- end
12
- end
13
-
14
- describe "invalid" do
15
- ["1 - :a", "2 * true"].each do |input|
16
- it input do
17
- expect { Code.evaluate(input) }.to raise_error(Code::Error)
18
- end
19
- end
20
- end
21
- end
data/spec/code_spec.rb DELETED
@@ -1,642 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- RSpec.describe Code do
6
- def format_input(input)
7
- Code::Format.format(described_class.parse(input))
8
- end
9
-
10
- def evaluate_with_output(input)
11
- output = StringIO.new
12
- result = described_class.evaluate(input, output: output)
13
- [result, output.string]
14
- end
15
-
16
- (
17
- [
18
- "{ a: 1, b: 2 }.transform_values { |key| key.upcase }",
19
- "{ a: { b: [1, 2, 3, 4] } }.dig(:a, :b, 2)",
20
- "{ a: 1, b: 2 }.transform_values { |key, value, index| index * 2 }",
21
- "{ a: 1, b: 2 }.transform_values { |key, value, index, dictionary| dictionary.a }",
22
- "sum = (a, b: 2) => { a + b } sum(1)",
23
- "Object.new !== Object.new"
24
- ] +
25
- %w[
26
- 1..3
27
- 1...3
28
- :abc.size
29
- Date.new.change
30
- {}.zero?
31
- {}.any?
32
- {}.many?
33
- 1.zero?
34
- 1.any?
35
- 1.many?
36
- 1.0.zero?
37
- 1.0.any?
38
- 1.0.many?
39
- [].zero?
40
- [].any?
41
- [].many?
42
- Base64
43
- Base64.new
44
- Smtp
45
- Smtp.new
46
- Time.monday?
47
- Time.tuesday?
48
- Time.wednesday?
49
- Time.thursday?
50
- Time.friday?
51
- Time.saturday?
52
- Time.sunday?
53
- Time.now.monday?
54
- Time.now.tuesday?
55
- Time.now.wednesday?
56
- Time.now.thursday?
57
- Time.now.friday?
58
- Time.now.saturday?
59
- Time.now.sunday?
60
- Time.now.second
61
- Time.now.seconds
62
- Time.now.minute
63
- Time.now.minutes
64
- Time.now.hour
65
- Time.now.hours
66
- Time.now.day
67
- Time.now.days
68
- Time.now.month
69
- Time.now.months
70
- Time.now.year
71
- Time.now.years
72
- Time.second
73
- Time.seconds
74
- Time.minute
75
- Time.minutes
76
- Time.hour
77
- Time.hours
78
- Time.day
79
- Time.days
80
- Time.month
81
- Time.months
82
- Time.year
83
- Time.years
84
- 1.day.ago
85
- 1.day.from_now
86
- 1.hour.ago
87
- 1.hour.from_now
88
- 2.days.ago
89
- 2.days.from_now
90
- 2.hours.ago
91
- 2.hours.ago.hour
92
- 2.hours.from_now
93
- 2.hours.from_now.hour
94
- Time.hour
95
- Date.hour
96
- Boolean.new
97
- Boolean.new(true)
98
- Boolean.new(false)
99
- Class.new
100
- Class.new(Boolean)
101
- Class.new(Class)
102
- Context.new
103
- Context.new(a:1)
104
- Date.new
105
- Date.new.hour
106
- Date.new("2024-03-05")
107
- Date.new("2024-03-05").hour
108
- Date.today
109
- Date.yesterday
110
- Date.tomorrow
111
- Date.tomorrow.hour
112
- Decimal.new
113
- Decimal.new(0)
114
- Decimal.new(1.2)
115
- Dictionary.new
116
- Dictionary.new(a:1)
117
- Duration.new
118
- Duration.new(1.day)
119
- Duration.new("P1D")
120
- Function.new
121
- Integer.new
122
- Integer.new(0)
123
- Integer.new(1)
124
- Integer.new(1.2)
125
- List.new
126
- List.new([])
127
- List.new([1,2])
128
- Nothing.new
129
- Nothing.new(1)
130
- Object.new
131
- Object.new(1)
132
- Range.new
133
- Range.new(1,2)
134
- Range.new(-1)
135
- Range.new(1,2,exclude_end:false)
136
- Range.new(1,2,exclude_end:true)
137
- String.new
138
- String.new(:hello)
139
- Time.new
140
- Time.new("2024-03-05.06:10:59.UTC")
141
- Time.now
142
- Time.tomorrow
143
- Time.yesterday
144
- Time.tomorrow
145
- Code.new
146
- Parameter.new
147
- IdentifierList.new
148
- IdentifierList.new([])
149
- Time.new(nothing).before?
150
- Json.parse('1')
151
- Json.parse('[]')
152
- Json.parse('{}')
153
- Json.parse('random-string')
154
- Ics.parse("BEGIN:VCALENDAR\nEND:VCALENDAR")
155
- {}["".to_string]
156
- ] + ["Time.hour >= 6 and Time.hour <= 23"]
157
- ).each do |input|
158
- it(input) do
159
- original_result, original_output = evaluate_with_output(input)
160
- formatted = format_input(input)
161
- formatted_result, formatted_output = evaluate_with_output(formatted)
162
-
163
- expect(formatted_result.class).to eq(original_result.class)
164
- expect(formatted_output).to eq(original_output)
165
- end
166
- end
167
-
168
- examples =
169
- [
170
- [
171
- "user = { name: :Dorian, age: 31 } user.delete_if(String) user.keys",
172
- '["age"]'
173
- ],
174
- [
175
- "(user = { name: :Dorian, age: 31 }).transform_values { user.name }.age",
176
- ":Dorian"
177
- ],
178
- ["{ end: 2 }.keys.first", ":end"],
179
- %w[!!1 true],
180
- %w[!!true true],
181
- %w[!false true],
182
- %w["" ''],
183
- %w["Hello".downcase :hello],
184
- %w['' ""],
185
- %w[+1 1],
186
- %w[+1.0 1.0],
187
- %w[+true true],
188
- %w[--1 1],
189
- %w[--1.0 1.0],
190
- %w[-1 -1],
191
- %w[-1.0 -1.0],
192
- %w[0 0],
193
- %w[0b10 2],
194
- %w[0o10 8],
195
- %w[0x10 16],
196
- %w[1.0e1 10.0],
197
- %w[1.1 1.1],
198
- %w[1.day.ago.after? false],
199
- %w[1.day.ago.after?(1.day.from_now) false],
200
- %w[1.day.ago.before? true],
201
- %w[1.day.ago.before?(1.day.from_now) true],
202
- %w[1.day.from_now.after? true],
203
- %w[1.day.from_now.after?(1.day.ago) true],
204
- %w[1.day.from_now.before? false],
205
- %w[1.day.from_now.before?(1.day.ago) false],
206
- %w[10e1.0 100.0],
207
- %w[1e1 10],
208
- %w[2.days.ago.future? false],
209
- %w[2.days.ago.past? true],
210
- %w[2.days.from_now.future? true],
211
- %w[2.days.from_now.past? false],
212
- %w[9975×14÷8 17456.25],
213
- %w["Hello".starts_with?("He") true],
214
- %w["Hello".starts_with?("lo") false],
215
- %w["Hello".start_with?("He") true],
216
- %w["Hello".start_with?("lo") false],
217
- %w["Hello".ends_with?("lo") true],
218
- %w["Hello".ends_with?("He") false],
219
- %w["Hello".end_with?("lo") true],
220
- %w["Hello".end_with?("He") false],
221
- %w[:Hello.include?(:H) true],
222
- %w[:admin? :admin?],
223
- %w[:hello :hello],
224
- %w[:update!.to_string :update!],
225
- %w[Boolean(1) true],
226
- %w[Boolean(2.days.ago) true],
227
- %w[Boolean(2.days.ago) true],
228
- %w[Boolean(false) false],
229
- %w[Boolean(nothing) false],
230
- %w[Boolean(true) true],
231
- %w[Boolean({}) true],
232
- %w[Boolean.new false],
233
- %w[Boolean.new(1) true],
234
- %w[Boolean.new(2.days.ago) true],
235
- %w[Boolean.new(2.days.ago) true],
236
- %w[Boolean.new(false) false],
237
- %w[Boolean.new(nothing) false],
238
- %w[Boolean.new(true) true],
239
- %w[Boolean.new({}) true],
240
- %w[Class(2.days.ago) Time],
241
- %w[Class(Boolean) Boolean],
242
- %w[Class(Time) Time],
243
- %w[Class(nothing) Nothing],
244
- %w[Class(true) Boolean],
245
- %w[String String],
246
- %w[Time Time],
247
- %w[Class Class],
248
- %w[Class.new Nothing],
249
- %w[Class.new Nothing],
250
- %w[Class.new(2.days.ago) Time],
251
- %w[Class.new(Boolean) Boolean],
252
- %w[Class.new(Date) Date],
253
- %w[Class.new(Time) Time],
254
- %w[Class.new(nothing) Nothing],
255
- %w[Class.new(true) Boolean],
256
- %w[Date Date],
257
- ["Date.format == Date.format(:default)", "true"],
258
- %w[Date("0001-01-01").to_string :0001-01-01],
259
- [
260
- "Date(\"2024-03-02\").format == Date(\"2024-03-02\").format(:default)",
261
- "true"
262
- ],
263
- %w[Date("2024-03-02").to_string :2024-03-02],
264
- %w[Decimal(1) 1.0],
265
- %w[Decimal(1) 1],
266
- %w[Decimal(:1) 1.0],
267
- %w[Decimal(:1) 1],
268
- %w[Decimal.new 0],
269
- %w[Decimal.new(1) 1.0],
270
- %w[Decimal.new(1) 1],
271
- %w[Decimal.new(:1) 1.0],
272
- %w[Decimal.new(:1) 1],
273
- %w[false false],
274
- %w[true true],
275
- %w[{} {}],
276
- ["'Hello Dorian'", '"Hello Dorian"'],
277
- ["'Hello \\{name}'", ':Hello + " \\{name}"'],
278
- ["'Hello {1}'", '"Hello 1"'],
279
- ["(true false)", "false"],
280
- ["1 & 1", "1"],
281
- ["1 + 1", "2"],
282
- ["1 < 2", "true"],
283
- ["1 << 1", "2"],
284
- ["1 <= 1", "true"],
285
- ["1 == 1 and 2 == 2", "true"],
286
- ["1 == 1", "true"],
287
- ["1 > 2", "false"],
288
- ["1 >= 1", "true"],
289
- ["1 >> 1", "0"],
290
- ["1 ^ 2", "3"],
291
- ["1 | 2", "3"],
292
- ["2 * 3 + 2", "8"],
293
- ["2 * 3", "6"],
294
- ["2 ** 3 ** 2", "512"],
295
- ["2 ** 3", "8"],
296
- ["2 + 2 * 3", "8"],
297
- ["2 / 4", "0.5"],
298
- ["3.times {}", "3"],
299
- ["4 % 3", "1"],
300
- ["Boolean([])", "true"],
301
- ["Boolean(true, false)", "true"],
302
- ["Boolean.new([])", "true"],
303
- ["Boolean.new(false, true)", "false"],
304
- ["Boolean.new(true, false)", "true"],
305
- ["Class(true, 1)", "Boolean"],
306
- ["Class.new(Boolean, Time)", "Boolean"],
307
- ["Class.new(Time, Boolean)", "Time"],
308
- %w[Date("2024-3-2").to_string :2024-03-02],
309
- %w[Date("2024-3-2").to_string :2024-03-02],
310
- %w[Time.format.present? true],
311
- ["Class(Time.format(locale: :fr))", "String"],
312
- [
313
- "Time.new(\"2024-03-05.06:10:59.UTC\").format(locale: :fr) == Time.new(\"2024-03-05.06:10:59.UTC\").format(:default, locale: :fr)",
314
- "true"
315
- ],
316
- [
317
- "Time.new(\"2024-03-05.06:10:59.UTC\").format == Time.new(\"2024-03-05.06:10:59.UTC\").format(:default)",
318
- "true"
319
- ],
320
- ["Decimal(1, :2)", "100"],
321
- ["Decimal(:1, 2)", "100.0"],
322
- ["Decimal.new(1, :2)", "100"],
323
- ["Decimal.new(:1, 2)", "100.0"],
324
- ["[,,].include?(4)", "false"],
325
- ["[,].include?(4)", "false"],
326
- ["[1, 2, 3, \n ].include?(4)", "false"],
327
- ["[1, 2, 3,].include?(4)", "false"],
328
- ["[1, 2, 3]", "[1, 2, 3]"],
329
- ["[1, 2, 3].include?(2)", "true"],
330
- ["[1, 2, 3].include?(4)", "false"],
331
- [
332
- "summary = { by_status: { draft: 1 } } k = \"draft\" summary.by_status[k]",
333
- "1"
334
- ],
335
- [
336
- "summary = { by_status: {} } k = \"draft\" summary.by_status[k] = 1 summary",
337
- '{"by_status" => {"draft" => 1}}'
338
- ],
339
- [
340
- "summary = { by_status: {} } [{status: \"draft\"}].each { |track| summary.by_status[track[:status]] = 1 } summary",
341
- '{"by_status" => {"draft" => 1}}'
342
- ],
343
- ["[1, 2, 3].map { |i| continue(0) if i.even? i ** 2}", "[1, 0, 9]"],
344
- ["[1, 2, 3].map { |i| next if i == 2 i ** 2}", "[1, nothing, 9]"],
345
- ["[1, 2, 3].map { |i| next(0) if i.even? i ** 2}", "[1, 0, 9]"],
346
- ["[1, 2, 3].select { |n| n.even? }", "[2]"],
347
- ["[1, 2, 3].select { |n| n.even? }.select { |n| n.odd? }", "[]"],
348
- ["[1, 2, 3, 4].uniq { |n| n % 2 }", "[1, 2]"],
349
- ['[1, "1", 2, "2"].uniq(String)', '["1", "2"]'],
350
- ["[1, 2, 3, 4].uniq(String)", "[]"],
351
- ["[1, 2].map(&:to_string)", "[:1, :2]"],
352
- ["[1, 2].map(&:to_string)", '["1", "2"]'],
353
- ["[1, 2].map(&:to_string)", '["1", "2"]'],
354
- ["[1].each do end", "[1]"],
355
- ["[[true]]", "[[true]]"],
356
- ["[]", "[]"],
357
- ["\r\n", "nothing"],
358
- ["a = 0 [1, 2, 3].each { |i| next if i == 2 a += i } a", "4"],
359
- [
360
- "a = 0\nb = 0\nwhile a < 4\n a += 1\n continue if a == 2\n b += a\nend\nb",
361
- "8"
362
- ],
363
- ["a = 0 loop a += 1 break end a", "1"],
364
- ["x = loop break(42) end x", "42"],
365
- ["a = 0\nuntil a > 10 a += 1 end a", "11"],
366
- ["a = 0\nwhile a < 10 a += 1 end a", "10"],
367
- ["a = 1 3.times { a += 1 } a", "4"],
368
- ["a = 1 a *= 2 a", "2"],
369
- ["a = 1 a += 1 a", "2"],
370
- ["a = 1 a += 1 a", "2"],
371
- ["a = 1 a -= 1 a", "0"],
372
- ["a = 3 a -= 1 if false a", "3"],
373
- ["a = 1 a = - 1 if false a", "1"],
374
- ["a = 3\n(a -= 1) if false\na", "3"],
375
- ["a = 1 a /= 2 a", "0.5"],
376
- ["a = 1 a <<= 1 a", "2"],
377
- ["a = 1 a >>= 1 a", "0"]
378
- ] +
379
- [
380
- ["a = 1 a ^= 2 a", "3"],
381
- ["a = 1 a |= 2 a", "3"],
382
- ["a = 1 a", "1"],
383
- ["a = 1 b = 2 c = a + b c", "3"],
384
- ["a = 1", "1"],
385
- ["a = 10 a %= 2 a", "0"],
386
- ["a = b = c = 1 a + b + c", "3"],
387
- ["a = false a &&= true a", "false"],
388
- ["a = false a ||= true a", "true"],
389
- ["a rescue :oops", ":oops"],
390
- ["false ? 1 : 2", "2"],
391
- ["false ? 1", ""],
392
- ["false || false", "false"],
393
- ["if false 1 else 2", "2"],
394
- ["if false 1 else if false 2", "nothing"],
395
- ["if false 1 else if true 2", "2"],
396
- ["if false 1 else if false 2 else 3 end", "3"],
397
- ["if false 1 else if true 2 else 3 end", "2"],
398
- ["if false 1 else unless false 2", "2"],
399
- ["if false 1 else unless true 2 else 3 end", "3"],
400
- ["if false 1 else unless true 2", "nothing"],
401
- ["if false 1 elsif false 2", "nothing"],
402
- ["if false 1 elsif true 2", "2"],
403
- ["if false 1", "nothing"],
404
- %w[break(5) 5],
405
- %w[continue(6) 6],
406
- ["if true 1", "1"],
407
- %w[next(7) 7],
408
- ["not not false", "false"],
409
- ["not true", "false"],
410
- %w[return(9) 9],
411
- ["f = () => { return(3) 4 } f()", "3"],
412
- ["a = 1 orirginal = 2 orirginal", "2"],
413
- ["a = 1 andy = 2 andy", "2"],
414
- ["ifonly = 1 ifonly", "1"],
415
- ["unlessy = 2 unlessy", "2"],
416
- ["elsiffy = 3 elsiffy", "3"],
417
- ["elsunlessy = 4 elsunlessy", "4"],
418
- ["elsewhere = 5 elsewhere", "5"],
419
- ["whiley = 6 whiley", "6"],
420
- ["untily = 7 untily", "7"],
421
- ["looped = 8 looped", "8"],
422
- ["doable = 9 doable", "9"],
423
- ["beginner = 10 beginner", "10"],
424
- ["endless = 11 endless", "11"],
425
- ["trueblue = 12 trueblue", "12"],
426
- ["falsey = 13 falsey", "13"],
427
- ["nothingness = 14 nothingness", "14"],
428
- ["random = 1 random", "1"],
429
- ["true && false", "false"],
430
- ["true && true", "true"],
431
- ["true ? 1 : 2", "1"],
432
- ["true ? 1", "1"],
433
- ["true and false", "false"],
434
- ["true or false", "true"],
435
- ["weekday? = false\n or true\nweekday?", "true"],
436
- ["true || false", "true"],
437
- ["unless false 1", "1"],
438
- ["unless true 1", "nothing"],
439
- ["until true", "nothing"],
440
- ["while false", "nothing"],
441
- ["{ a: 1, b: 2, c: 3 }", '{"a" => 1, "b" => 2, "c" => 3}'],
442
- ['"Hello Dorian"', '"Hello Dorian"'],
443
- ['"Hello \\{name}"', '"Hello \\{" + "name}"'],
444
- ['"Hello {1}"', '"Hello 1"'],
445
- ['user = {} user.name = "Dorian" user.name', ":Dorian"],
446
- ['user = {} user[:name] = "Dorian" user[:name]', ":Dorian"],
447
- [
448
- 'value = {} email = "a" calendar_id = "c" value[email] ||= {} value[email][calendar_id] ||= {} value[email][calendar_id]',
449
- "{}"
450
- ],
451
- ['{ "first_name": "Dorian" }', '{"first_name" => "Dorian"}'],
452
- ['{ "first_name": "Dorian" }.as_json', '{"first_name" => "Dorian"}'],
453
- %w[nothing.to_json :null],
454
- %w[1.to_json "1"],
455
- %w[1.0.to_json '"1.0"'],
456
- %w[1.1.to_json '"1.1"'],
457
- ["a = {} a.merge!(a: 1) a", "{a: 1}"],
458
- ["a = {} a.merge(a: 1) a", "{}"],
459
- %w[1&.even? false],
460
- ["nothing&.even? || 1", "1"],
461
- ["nothing&.even? && 1", "nothing"],
462
- %w[2&.even? true],
463
- ["a = 1 a&.even?", "false"],
464
- ["a = 2 a&.even?", "true"],
465
- ["a = nothing a&.even?", "nothing"],
466
- ["false && puts(:Hello)", "false"],
467
- ["true || puts(:Hello)", "true"],
468
- ["false and puts(:Hello)", "false"],
469
- ["true or puts(:Hello)", "true"],
470
- ["[1, 2].join", "'12'"],
471
- ["[1, 2].join(',')", "'1,2'"],
472
- ["[nothing, 2].join(',')", "',2'"],
473
- ["[nothing, nothing].join", "''"],
474
- ["[1, 2].select(&:even?)", "[2]"],
475
- ["[1, 2].reject(&:even?)", "[1]"],
476
- ["a = [1, 2] a.select!(&:even?) a", "[2]"],
477
- ["a = [1, 2] a.reject!(&:even?) a", "[1]"],
478
- ["[1, 2].map(&:even?)", "[false, true]"]
479
- ] +
480
- [
481
- ["a = [1, 2] a.map!(&:even?) a", "[false, true]"],
482
- ["Html.p { \"Hello\" }.to_html", "'<p>Hello</p>'"],
483
- %w[Html.br.to_html '<br>'],
484
- [
485
- "Html.div { Html.div { Html.span { :Nested } } }.to_html",
486
- "'<div><div><span>Nested</span></div></div>'"
487
- ],
488
- [
489
- "Html.div { Html.p { Html.span { :hello } } }.to_html",
490
- "'<div><p><span>hello</span></p></div>'"
491
- ],
492
- [
493
- "Html.join([Html.p { :hello }, Html.p { :world }], Html.br).to_html",
494
- "'<p>hello</p><br><p>world</p>'"
495
- ],
496
- %w[Html.join.to_html ''],
497
- [
498
- "Html.div { Html.text(\"<span>\") }.to_html",
499
- "'<div>&lt;span&gt;</div>'"
500
- ],
501
- [
502
- "Html.div { Html.raw(\"<span>ok</span>\") }.to_html",
503
- "'<div><span>ok</span></div>'"
504
- ],
505
- ["Html.escape(\"<span>&</span>\")", "'&lt;span&gt;&amp;&lt;/span&gt;'"],
506
- ["Html.unescape(\"A&nbsp;&nbsp;B &amp; C\")", "'A  B & C'"],
507
- ["[1, 2, 3].any?", "true"],
508
- ["[1, 2, 3].any?(&:even?)", "true"],
509
- ["[1, 2, 3].none?", "false"],
510
- ["[1, 2, 3].none?(&:even?)", "false"],
511
- ["subject = 1 { subject }", "{ subject: 1 }"],
512
- ["subject = 1 { subject: }", "{ subject: 1 }"],
513
- ["'{1} {2}'", "'1 2'"],
514
- ['Time.zone = "Etc/UTC"', '"Etc/UTC"'],
515
- %w[Json.parse("1") 1],
516
- %w[{a:1}.to_query "a=1"],
517
- ["dorian = 1 dorian#.to_something", "1"],
518
- ["", ""]
519
- ]
520
-
521
- examples.each do |input, expected|
522
- it "#{input} == #{expected}" do
523
- formatted = format_input(input)
524
-
525
- output = StringIO.new
526
- code_input = described_class.evaluate(input, output: output)
527
- code_expected = described_class.evaluate(expected)
528
- expect(code_input).to eq(code_expected)
529
- expect(output.string).to eq("")
530
-
531
- formatted_output = StringIO.new
532
- formatted_result =
533
- described_class.evaluate(formatted, output: formatted_output)
534
- expect(formatted_result).to eq(code_input)
535
- expect(formatted_output.string).to eq("")
536
- next if code_input.is_a?(Code::Object::Decimal)
537
-
538
- expect(code_input.to_json).to eq(code_expected.to_json)
539
- YAML.unsafe_load(code_input.to_yaml)
540
- YAML.unsafe_load(code_expected.to_yaml)
541
- end
542
- end
543
-
544
- [["puts(true)", "true\n"], %w[print(false) false]].each do |input, expected|
545
- it "#{input} prints #{expected}" do
546
- formatted = format_input(input)
547
-
548
- output = StringIO.new
549
- described_class.evaluate(input, output: output)
550
- expect(output.string).to eq(expected)
551
-
552
- formatted_output = StringIO.new
553
- described_class.evaluate(formatted, output: formatted_output)
554
- expect(formatted_output.string).to eq(expected)
555
- end
556
- end
557
-
558
- it "doesn't crash with dictionnary as parameter" do
559
- input = <<~INPUT
560
- [
561
- {
562
- videos: [{}]
563
- },
564
- {
565
- videos: [{}]
566
- }
567
- ].map do |post|
568
- post.videos.map { |video| }
569
- end
570
- INPUT
571
- described_class.evaluate(input)
572
- described_class.evaluate(format_input(input))
573
- end
574
-
575
- it "doesn't crash with functions" do
576
- input = <<~INPUT
577
- send! = (subject:) => { subject }
578
-
579
- send!(subject: "pomodoro start")
580
- send!(subject: "pomodoro break")
581
- send!(subject: "pomodoro start")
582
- send!(subject: "pomodoro break")
583
- INPUT
584
- described_class.evaluate(input)
585
- described_class.evaluate(format_input(input))
586
- end
587
-
588
- it "raises for undefined constant receiver assignment" do
589
- expect do
590
- described_class.evaluate("UnknownConstant.zone = 1")
591
- end.to raise_error(Code::Error, /UnknownConstant is not defined/)
592
- end
593
-
594
- it "parses ics events into dictionaries" do
595
- result = described_class.evaluate(<<~CODE)
596
- events = Ics.parse("BEGIN:VCALENDAR
597
- VERSION:2.0
598
- PRODID:-//code-ruby//EN
599
- BEGIN:VEVENT
600
- UID:event-1
601
- DTSTART:20260327T120000Z
602
- DTEND:20260327T133000Z
603
- SUMMARY:Lunch
604
- DESCRIPTION:Team lunch
605
- LOCATION:Paris
606
- URL:https://example.com/events/1
607
- CATEGORIES:food,team
608
- END:VEVENT
609
- END:VCALENDAR")
610
-
611
- {
612
- size: events.size,
613
- summary: events.first.summary,
614
- location: events.first.location,
615
- uid: events.first.uid,
616
- categories: events.first.categories,
617
- starts_at: events.first.starts_at.to_string,
618
- ends_at: events.first.ends_at.to_string,
619
- all_day: events.first.all_day
620
- }
621
- CODE
622
-
623
- expect(result).to eq(
624
- {
625
- size: 1,
626
- summary: "Lunch",
627
- location: "Paris",
628
- uid: "event-1",
629
- categories: %w[food team],
630
- starts_at: "2026-03-27 12:00:00 UTC",
631
- ends_at: "2026-03-27 13:30:00 UTC",
632
- all_day: false
633
- }.to_code
634
- )
635
- end
636
-
637
- it "returns an empty list for invalid ics" do
638
- expect(described_class.evaluate('Ics.parse("not an ics file")')).to eq(
639
- [].to_code
640
- )
641
- end
642
- end
data/spec/spec_helper.rb DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../lib/code-ruby"
4
- require "sinatra"
5
- require "webmock/rspec"
6
-
7
- class FakeHttpBin < Sinatra::Base
8
- set :host_authorization, { permitted_hosts: ["httpbin.org"] }
9
-
10
- %w[GET HEAD POST PUT DELETE OPTIONS TRACE PATCH].each do |verb|
11
- route verb, "/status/:status" do
12
- status params[:status].to_i
13
- end
14
- end
15
- end
16
-
17
- WebMock.disable_net_connect!
18
-
19
- RSpec.configure do |config|
20
- config.before { stub_request(:any, /httpbin\.org/).to_rack(FakeHttpBin) }
21
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Zeitwerk::Loader do
4
- it "eager loads without treating monkey patches as Code::Extensions constants" do
5
- expect { described_class.eager_load_all }.not_to raise_error
6
- end
7
- end