code-ruby 3.0.2 → 3.0.4
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 +4 -4
- data/Gemfile.lock +8 -1
- data/VERSION +1 -1
- data/code-ruby.gemspec +1 -0
- data/lib/code/object/global.rb +3 -0
- data/lib/code/object/ics.rb +113 -0
- data/lib/code/object/string.rb +6 -2
- data/lib/code-ruby.rb +1 -0
- data/spec/code/object/ics_spec.rb +50 -0
- data/spec/code_spec.rb +48 -0
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a1d60879ca94a53452e814d8c8de4975be88088beb0ce216f3584a48b0dfcae
|
|
4
|
+
data.tar.gz: 0ad78db73acc96f99df57e3c945c5d97fb404ed692d5f9de7c7bc08c25562468
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2eec31b74d22e1d123ea138cf41063792cbff5e2192e26a692c7dfaa52834db9112bacb9929d3a88d59a6fd4296cc23ff0501a41761f95fa963b027878eda54c
|
|
7
|
+
data.tar.gz: 3363b4f05ca9203842aa8c3a6eee0ccd2ac570609cd9bf3bfb77bda134b234c2ddd0e95a9265941ac8239659260823dfc8e36c8fb34643d835cc702dbc5e5ae4
|
data/Gemfile.lock
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
code-ruby (3.0.
|
|
4
|
+
code-ruby (3.0.4)
|
|
5
5
|
activesupport
|
|
6
6
|
base64
|
|
7
7
|
bigdecimal
|
|
8
8
|
did-you-mean
|
|
9
9
|
dorian-arguments
|
|
10
|
+
icalendar
|
|
10
11
|
json
|
|
11
12
|
language-ruby
|
|
12
13
|
mail
|
|
@@ -95,6 +96,12 @@ GEM
|
|
|
95
96
|
strscan (>= 3.1.2)
|
|
96
97
|
i18n (1.14.8)
|
|
97
98
|
concurrent-ruby (~> 1.0)
|
|
99
|
+
icalendar (2.12.2)
|
|
100
|
+
base64
|
|
101
|
+
ice_cube (~> 0.16)
|
|
102
|
+
logger
|
|
103
|
+
ostruct
|
|
104
|
+
ice_cube (0.17.0)
|
|
98
105
|
json (2.18.1)
|
|
99
106
|
json-schema (6.1.0)
|
|
100
107
|
addressable (~> 2.8)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.4
|
data/code-ruby.gemspec
CHANGED
data/lib/code/object/global.rb
CHANGED
|
@@ -167,6 +167,9 @@ class Code
|
|
|
167
167
|
when "Json"
|
|
168
168
|
sig(args) { Object.repeat }
|
|
169
169
|
code_arguments.any? ? Json.new(*code_arguments.raw) : Class.new(Json)
|
|
170
|
+
when "Ics"
|
|
171
|
+
sig(args) { Object.repeat }
|
|
172
|
+
code_arguments.any? ? Ics.new(*code_arguments.raw) : Class.new(Ics)
|
|
170
173
|
when "evaluate"
|
|
171
174
|
sig(args) { Object }
|
|
172
175
|
Code.code_evaluate(code_value.code_to_string, **globals)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Code
|
|
4
|
+
class Object
|
|
5
|
+
class Ics < Object
|
|
6
|
+
EVENT_ATTRIBUTES = %i[
|
|
7
|
+
uid
|
|
8
|
+
summary
|
|
9
|
+
description
|
|
10
|
+
location
|
|
11
|
+
url
|
|
12
|
+
status
|
|
13
|
+
organizer
|
|
14
|
+
categories
|
|
15
|
+
attendees
|
|
16
|
+
geo
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def self.call(**args)
|
|
20
|
+
code_operator = args.fetch(:operator, nil).to_code
|
|
21
|
+
code_arguments = args.fetch(:arguments, []).to_code
|
|
22
|
+
code_value = code_arguments.code_first
|
|
23
|
+
|
|
24
|
+
case code_operator.to_s
|
|
25
|
+
when "parse"
|
|
26
|
+
sig(args) { String }
|
|
27
|
+
code_parse(code_value)
|
|
28
|
+
else
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.code_parse(value)
|
|
34
|
+
source = value.to_code.raw
|
|
35
|
+
calendars = ::Icalendar::Calendar.parse(source)
|
|
36
|
+
calendars
|
|
37
|
+
.flat_map(&:events)
|
|
38
|
+
.map { |event| serialize_event(event) }
|
|
39
|
+
.to_code
|
|
40
|
+
rescue StandardError
|
|
41
|
+
[].to_code
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.serialize_event(event)
|
|
45
|
+
EVENT_ATTRIBUTES.each_with_object({}) do |attribute, result|
|
|
46
|
+
next unless event.respond_to?(attribute)
|
|
47
|
+
|
|
48
|
+
serialized = serialize_value(event.public_send(attribute))
|
|
49
|
+
serialized =
|
|
50
|
+
if attribute == :categories && serialized.is_a?(::Array)
|
|
51
|
+
serialized.flatten(1)
|
|
52
|
+
elsif scalar_event_attribute?(attribute) && serialized.is_a?(::Array)
|
|
53
|
+
serialized.join(",")
|
|
54
|
+
else
|
|
55
|
+
serialized
|
|
56
|
+
end
|
|
57
|
+
result[attribute] = serialized unless serialized.nil?
|
|
58
|
+
end.merge(
|
|
59
|
+
timestamp: serialize_value(event.dtstamp),
|
|
60
|
+
starts_at: serialize_value(event.dtstart),
|
|
61
|
+
ends_at: serialize_value(event.dtend),
|
|
62
|
+
all_day: !!serialize_date_like(event.dtstart).is_a?(::Date)
|
|
63
|
+
).compact
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.serialize_value(value)
|
|
67
|
+
case value
|
|
68
|
+
when nil
|
|
69
|
+
nil
|
|
70
|
+
when ::String
|
|
71
|
+
normalize_string(value)
|
|
72
|
+
when ::Symbol, ::Integer, ::Float, ::BigDecimal, true, false
|
|
73
|
+
value
|
|
74
|
+
when ::Array
|
|
75
|
+
value.map { |item| serialize_value(item) }
|
|
76
|
+
when ::Hash
|
|
77
|
+
value.transform_values { |item| serialize_value(item) }
|
|
78
|
+
else
|
|
79
|
+
serialized_date = serialize_date_like(value)
|
|
80
|
+
return serialized_date unless serialized_date.nil?
|
|
81
|
+
|
|
82
|
+
if value.respond_to?(:value)
|
|
83
|
+
serialize_value(value.value)
|
|
84
|
+
elsif value.respond_to?(:to_ical)
|
|
85
|
+
normalize_string(value.to_ical)
|
|
86
|
+
else
|
|
87
|
+
normalize_string(value.to_s)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.scalar_event_attribute?(attribute)
|
|
93
|
+
!%i[categories attendees geo].include?(attribute)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.normalize_string(value)
|
|
97
|
+
value
|
|
98
|
+
.dup
|
|
99
|
+
.force_encoding(::Encoding::UTF_8)
|
|
100
|
+
.encode(::Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.serialize_date_like(value)
|
|
104
|
+
case value
|
|
105
|
+
when ::Time, ::Date, ::ActiveSupport::TimeWithZone
|
|
106
|
+
value
|
|
107
|
+
when ::DateTime
|
|
108
|
+
value.to_time
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/code/object/string.rb
CHANGED
|
@@ -74,7 +74,7 @@ class Code
|
|
|
74
74
|
sig(args)
|
|
75
75
|
code_strip
|
|
76
76
|
when "split"
|
|
77
|
-
sig(args) { String }
|
|
77
|
+
sig(args) { String.maybe }
|
|
78
78
|
code_split(code_value)
|
|
79
79
|
else
|
|
80
80
|
super
|
|
@@ -162,7 +162,11 @@ class Code
|
|
|
162
162
|
def code_split(value)
|
|
163
163
|
code_value = value.to_code
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
if code_value.nothing?
|
|
166
|
+
List.new(raw.split)
|
|
167
|
+
else
|
|
168
|
+
List.new(raw.split(code_value.to_s))
|
|
169
|
+
end
|
|
166
170
|
end
|
|
167
171
|
|
|
168
172
|
def present?
|
data/lib/code-ruby.rb
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Code::Object::Ics do
|
|
6
|
+
it "normalizes binary event strings before json serialization" do
|
|
7
|
+
source =
|
|
8
|
+
<<~ICS
|
|
9
|
+
BEGIN:VCALENDAR
|
|
10
|
+
VERSION:2.0
|
|
11
|
+
PRODID:-//code-ruby//EN
|
|
12
|
+
BEGIN:VEVENT
|
|
13
|
+
UID:event-1
|
|
14
|
+
DTSTART:20260327T120000Z
|
|
15
|
+
DTEND:20260327T133000Z
|
|
16
|
+
SUMMARY:Joséphine
|
|
17
|
+
DESCRIPTION:https://luma.com/opv3owre
|
|
18
|
+
END:VEVENT
|
|
19
|
+
END:VCALENDAR
|
|
20
|
+
ICS
|
|
21
|
+
|
|
22
|
+
events = described_class.code_parse(Code::Object::String.new(source.b))
|
|
23
|
+
|
|
24
|
+
expect { expect(events.to_json).to include("Joséphine") }.not_to output.to_stderr
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "serializes comma-separated descriptions as strings" do
|
|
28
|
+
source =
|
|
29
|
+
<<~ICS
|
|
30
|
+
BEGIN:VCALENDAR
|
|
31
|
+
VERSION:2.0
|
|
32
|
+
PRODID:-//code-ruby//EN
|
|
33
|
+
BEGIN:VEVENT
|
|
34
|
+
UID:event-2
|
|
35
|
+
DTSTART:20260401T170000Z
|
|
36
|
+
DTEND:20260401T190000Z
|
|
37
|
+
SUMMARY:Le Cirque du Donut
|
|
38
|
+
DESCRIPTION:Bonjour Paris !\\nLe 1er avril, Kate Raworth vous donne rendez-vous, pour une soirée inédite.
|
|
39
|
+
END:VEVENT
|
|
40
|
+
END:VCALENDAR
|
|
41
|
+
ICS
|
|
42
|
+
|
|
43
|
+
events = described_class.code_parse(Code::Object::String.new(source))
|
|
44
|
+
description = events.raw.first.code_get("description").raw
|
|
45
|
+
|
|
46
|
+
expect(description).to be_a(String)
|
|
47
|
+
expect(description).to include("Le 1er avril, Kate Raworth")
|
|
48
|
+
expect(description).to include("rendez-vous, pour")
|
|
49
|
+
end
|
|
50
|
+
end
|
data/spec/code_spec.rb
CHANGED
|
@@ -151,6 +151,7 @@ RSpec.describe Code do
|
|
|
151
151
|
Json.parse('[]')
|
|
152
152
|
Json.parse('{}')
|
|
153
153
|
Json.parse('random-string')
|
|
154
|
+
Ics.parse("BEGIN:VCALENDAR\nEND:VCALENDAR")
|
|
154
155
|
{}["".to_string]
|
|
155
156
|
] + ["Time.hour >= 6 and Time.hour <= 23"]
|
|
156
157
|
).each do |input|
|
|
@@ -557,4 +558,51 @@ RSpec.describe Code do
|
|
|
557
558
|
described_class.evaluate("UnknownConstant.zone = 1")
|
|
558
559
|
end.to raise_error(Code::Error, /UnknownConstant is not defined/)
|
|
559
560
|
end
|
|
561
|
+
|
|
562
|
+
it "parses ics events into dictionaries" do
|
|
563
|
+
result = described_class.evaluate(<<~CODE)
|
|
564
|
+
events = Ics.parse("BEGIN:VCALENDAR
|
|
565
|
+
VERSION:2.0
|
|
566
|
+
PRODID:-//code-ruby//EN
|
|
567
|
+
BEGIN:VEVENT
|
|
568
|
+
UID:event-1
|
|
569
|
+
DTSTART:20260327T120000Z
|
|
570
|
+
DTEND:20260327T133000Z
|
|
571
|
+
SUMMARY:Lunch
|
|
572
|
+
DESCRIPTION:Team lunch
|
|
573
|
+
LOCATION:Paris
|
|
574
|
+
URL:https://example.com/events/1
|
|
575
|
+
CATEGORIES:food,team
|
|
576
|
+
END:VEVENT
|
|
577
|
+
END:VCALENDAR")
|
|
578
|
+
|
|
579
|
+
{
|
|
580
|
+
size: events.size,
|
|
581
|
+
summary: events.first.summary,
|
|
582
|
+
location: events.first.location,
|
|
583
|
+
uid: events.first.uid,
|
|
584
|
+
categories: events.first.categories,
|
|
585
|
+
starts_at: events.first.starts_at.to_string,
|
|
586
|
+
ends_at: events.first.ends_at.to_string,
|
|
587
|
+
all_day: events.first.all_day
|
|
588
|
+
}
|
|
589
|
+
CODE
|
|
590
|
+
|
|
591
|
+
expect(result).to eq(
|
|
592
|
+
{
|
|
593
|
+
size: 1,
|
|
594
|
+
summary: "Lunch",
|
|
595
|
+
location: "Paris",
|
|
596
|
+
uid: "event-1",
|
|
597
|
+
categories: ["food", "team"],
|
|
598
|
+
starts_at: "2026-03-27 12:00:00 UTC",
|
|
599
|
+
ends_at: "2026-03-27 13:30:00 UTC",
|
|
600
|
+
all_day: false
|
|
601
|
+
}.to_code
|
|
602
|
+
)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
it "returns an empty list for invalid ics" do
|
|
606
|
+
expect(described_class.evaluate('Ics.parse("not an ics file")')).to eq([].to_code)
|
|
607
|
+
end
|
|
560
608
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: code-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dorian Marié
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: icalendar
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: json
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -275,6 +289,7 @@ files:
|
|
|
275
289
|
- lib/code/object/global.rb
|
|
276
290
|
- lib/code/object/html.rb
|
|
277
291
|
- lib/code/object/http.rb
|
|
292
|
+
- lib/code/object/ics.rb
|
|
278
293
|
- lib/code/object/identifier_list.rb
|
|
279
294
|
- lib/code/object/integer.rb
|
|
280
295
|
- lib/code/object/json.rb
|
|
@@ -306,6 +321,7 @@ files:
|
|
|
306
321
|
- spec/code/object/decimal_spec.rb
|
|
307
322
|
- spec/code/object/dictionary_spec.rb
|
|
308
323
|
- spec/code/object/function_spec.rb
|
|
324
|
+
- spec/code/object/ics_spec.rb
|
|
309
325
|
- spec/code/object/integer_spec.rb
|
|
310
326
|
- spec/code/object/list_spec.rb
|
|
311
327
|
- spec/code/object/nothing_spec.rb
|