cal-invite 0.2.0 → 0.3.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.
@@ -3,166 +3,21 @@
3
3
  # lib/cal_invite/providers/ical.rb
4
4
  module CalInvite
5
5
  module Providers
6
- # iCalendar format provider for generating ICS (iCalendar) content.
7
- # This provider generates calendar content following the iCalendar specification (RFC 5545).
8
- # It supports single events, all-day events, and multi-day sessions with proper timezone handling.
6
+ # iCalendar provider (`:ical`), for Apple Calendar and other iCalendar apps.
7
+ # Produces the same RFC 5545 content as {Ics}; kept as its own provider
8
+ # symbol for backward compatibility.
9
9
  #
10
- # @example Creating a regular event
10
+ # @example
11
11
  # event = CalInvite::Event.new(
12
12
  # title: "Team Meeting",
13
13
  # start_time: Time.now,
14
14
  # end_time: Time.now + 3600,
15
15
  # timezone: 'America/New_York'
16
16
  # )
17
- # ical = CalInvite::Providers::Ical.new(event)
18
- # ics_content = ical.generate
19
- #
20
- # @example Creating a multi-day event
21
- # event = CalInvite::Event.new(
22
- # title: "Conference",
23
- # multi_day_sessions: [
24
- # { start_time: Time.now, end_time: Time.now + 3600 },
25
- # { start_time: Time.now + 86400, end_time: Time.now + 90000 }
26
- # ]
27
- # )
28
17
  # ics_content = CalInvite::Providers::Ical.new(event).generate
29
- class Ical < BaseProvider
30
- # Generates an iCalendar format string containing the event details.
31
- # Includes proper calendar headers, timezone information (if needed),
32
- # and one or more event blocks.
33
- #
34
- # @return [String] The complete iCalendar format content
35
- def generate
36
- [
37
- "BEGIN:VCALENDAR",
38
- "VERSION:2.0",
39
- "PRODID:-//CalInvite//Ruby//EN",
40
- "CALSCALE:GREGORIAN",
41
- "METHOD:#{method_value}",
42
- (event.calendar_name ? "X-WR-CALNAME:#{escape_text(event.calendar_name)}" : nil),
43
- generate_timezone,
44
- generate_events,
45
- "END:VCALENDAR"
46
- ].compact.join("\r\n")
47
- end
48
-
49
- private
50
-
51
- # Generates the event blocks (VEVENT) for the calendar.
52
- # Handles both single events and multi-day sessions.
53
- #
54
- # @return [String] The formatted event block(s)
55
- def generate_events
56
- if event.multi_day_sessions.any?
57
- event.multi_day_sessions.map { |session| generate_vevent(session) }.join("\r\n")
58
- else
59
- generate_vevent
60
- end
61
- end
62
-
63
- # Generates a single VEVENT block with all event details.
64
- # Handles both all-day events and time-specific events.
65
- #
66
- # @param session [Hash, nil] Optional session details for multi-day events
67
- # @return [String] The formatted VEVENT block
68
- # @raise [ArgumentError] If required time fields are missing for non-all-day events
69
- def generate_vevent(session = nil)
70
- lines = ["BEGIN:VEVENT"]
71
-
72
- if event.all_day
73
- start_date = event.start_time || Time.now
74
- end_date = event.end_time || (start_date + 86400)
75
- lines << "DTSTART;VALUE=DATE:#{format_date(start_date)}"
76
- lines << "DTEND;VALUE=DATE:#{format_date(end_date)}"
77
- else
78
- start_time = session ? session[:start_time] : event.start_time
79
- end_time = session ? session[:end_time] : event.end_time
80
- raise ArgumentError, "Start time is required for non-all-day events" unless start_time
81
- raise ArgumentError, "End time is required for non-all-day events" unless end_time
82
- lines << "DTSTART;TZID=#{event.timezone}:#{format_local_time(start_time)}"
83
- lines << "DTEND;TZID=#{event.timezone}:#{format_local_time(end_time)}"
84
- end
85
-
86
- # Required fields
87
- lines.concat([
88
- "SUMMARY:#{escape_text(event.title)}",
89
- "UID:#{event.uid}",
90
- "DTSTAMP:#{format_timestamp(Time.now.utc)}"
91
- ])
92
-
93
- # Optional fields
94
- lines << "DESCRIPTION:#{escape_text(format_description)}" if format_description
95
- lines << "LOCATION:#{escape_text(format_location)}" if format_location
96
- lines << "URL:#{escape_text(format_url)}" if format_url
97
- lines << geo_line if geo_line
98
- lines << organizer_line if organizer_line
99
-
100
- # Attendees
101
- attendees_list.each { |attendee| lines << attendee_line(attendee) }
102
-
103
- lines << rrule_line if rrule_line
104
- lines << "SEQUENCE:#{event.sequence}"
105
- lines << status_line
106
- lines << transp_line
107
- lines << busystatus_line
108
- lines << class_line
109
- lines.concat(importance_lines)
110
- lines << disallow_counter_line if disallow_counter_line
111
- lines.concat(valarm_lines)
112
- lines << "END:VEVENT"
113
- lines.join("\r\n")
114
- end
115
-
116
- # Generates the timezone block (VTIMEZONE) for the calendar, with real
117
- # STANDARD/DAYLIGHT observances derived from the timezone's transition rules.
118
- # Only included for non-all-day events with a recognized, non-UTC timezone.
119
- #
120
- # @return [String, nil] The formatted timezone block, or nil if not applicable
121
- def generate_timezone
122
- return nil if event.all_day # No timezone needed for all-day events
123
-
124
- vtimezone_lines&.join("\r\n")
125
- end
126
-
127
- # Formats a time object as a date string in iCalendar format.
128
- #
129
- # @param time [Time] The time to format
130
- # @return [String] The formatted date (YYYYMMDD)
131
- def format_date(time)
132
- time.strftime("%Y%m%d")
133
- end
134
-
135
- # Formats a time object as a local time string in iCalendar format.
136
- # Converts from UTC to the event's timezone; times are expected to be in
137
- # UTC already.
138
- #
139
- # @param time [Time] The time to format
140
- # @return [String] The formatted local time (YYYYMMDDTHHmmSS)
141
- def format_local_time(time)
142
- local_wall_time(time).strftime("%Y%m%dT%H%M%S")
143
- end
144
-
145
- # Formats a time object as an UTC timestamp in iCalendar format.
146
- #
147
- # @param time [Time] The time to format
148
- # @return [String] The formatted UTC timestamp (YYYYMMDDTHHmmSSZ)
149
- def format_timestamp(time)
150
- time.strftime("%Y%m%dT%H%M%SZ")
151
- end
152
-
153
- # Escapes special characters in text according to iCalendar spec.
154
- # Handles backslashes, newlines, commas, and semicolons.
155
- #
156
- # @param text [String, nil] The text to escape
157
- # @return [String] The escaped text
158
- def escape_text(text)
159
- return '' if text.nil?
160
- text.to_s
161
- .gsub('\\', '\\\\')
162
- .gsub("\n", '\\n')
163
- .gsub(',', '\\,')
164
- .gsub(';', '\\;')
165
- end
18
+ #
19
+ # @see Ics
20
+ class Ical < Ics
166
21
  end
167
22
  end
168
23
  end
@@ -7,6 +7,9 @@ module CalInvite
7
7
  # This provider generates ICS files that are compatible with most calendar applications.
8
8
  # Supports all-day events, regular events, and multi-day sessions with proper timezone handling.
9
9
  #
10
+ # This is the single RFC 5545 generator for the gem: {Ical} and {IcsContent}
11
+ # are subclasses kept for their public names and produce identical output.
12
+ #
10
13
  # @example Creating a regular event ICS file
11
14
  # event = CalInvite::Event.new(
12
15
  # title: "Team Meeting",
@@ -66,21 +69,60 @@ module CalInvite
66
69
  end
67
70
 
68
71
  calendar_lines << "END:VCALENDAR"
69
- calendar_lines.join("\r\n")
72
+ ensure_single_line_values!(calendar_lines)
73
+ calendar_lines.map { |line| fold_line(line) }.join("\r\n")
70
74
  end
71
75
 
72
76
  private
73
77
 
78
+ # Maximum content line length in octets, excluding the CRLF (RFC 5545 §3.1).
79
+ MAX_LINE_OCTETS = 75
80
+
81
+ # Folds a content line longer than 75 octets into CRLF + single-space
82
+ # continuation lines (RFC 5545 §3.1). Splits only between characters, so a
83
+ # multi-byte UTF-8 character is never cut in half.
84
+ #
85
+ # @param line [String] A single unfolded content line
86
+ # @return [String] The folded line (unchanged if already short enough)
87
+ def fold_line(line)
88
+ return line if line.bytesize <= MAX_LINE_OCTETS
89
+
90
+ segments = [+""]
91
+ line.each_char do |char|
92
+ segments << +" " if segments.last.bytesize + char.bytesize > MAX_LINE_OCTETS
93
+ segments.last << char
94
+ end
95
+ segments.join("\r\n")
96
+ end
97
+
98
+ # Last line of defense against content-line injection: a CR or LF inside a
99
+ # value would end the line and let the rest of the value be parsed as new
100
+ # properties/components. TEXT values are escaped before reaching here, so
101
+ # this only fires for unescaped values (UID, URL, RRULE, emails, ...).
102
+ #
103
+ # @param lines [Array<String>] The content lines about to be joined
104
+ # @raise [ArgumentError] if any line contains a CR or LF
105
+ # @return [void]
106
+ def ensure_single_line_values!(lines)
107
+ line = lines.find { |l| l.match?(/[\r\n]/) }
108
+ return unless line
109
+
110
+ raise ArgumentError, "#{line[/\A[A-Z-]+/]} value must not contain line breaks"
111
+ end
112
+
74
113
  # Generates an all-day event component (VEVENT) with appropriate formatting.
114
+ # Defaults to today (and a one-day span) when start/end times are omitted.
75
115
  #
76
116
  # @return [Array<String>] Array of iCalendar format lines for the all-day event
77
117
  def generate_all_day_event
118
+ start_date = event.start_time || Time.now
119
+ end_date = event.end_time || (start_date + 86400)
78
120
  vevent = [
79
121
  "BEGIN:VEVENT",
80
122
  "UID:#{event.uid}",
81
123
  "DTSTAMP:#{format_timestamp(Time.now.utc)}",
82
- "DTSTART;VALUE=DATE:#{format_date(event.start_time)}",
83
- "DTEND;VALUE=DATE:#{format_date(event.end_time)}",
124
+ "DTSTART;VALUE=DATE:#{format_date(start_date)}",
125
+ "DTEND;VALUE=DATE:#{format_date(end_date)}",
84
126
  "SUMMARY:#{escape_text(event.title)}"
85
127
  ]
86
128
  add_optional_fields(vevent)
@@ -98,8 +140,8 @@ module CalInvite
98
140
  "BEGIN:VEVENT",
99
141
  "UID:#{event.uid}",
100
142
  "DTSTAMP:#{format_timestamp(Time.now.utc)}",
101
- "DTSTART;TZID=#{event.timezone}:#{format_local_timestamp(start_time)}",
102
- "DTEND;TZID=#{event.timezone}:#{format_local_timestamp(end_time)}",
143
+ date_time_line("DTSTART", start_time),
144
+ date_time_line("DTEND", end_time),
103
145
  "SUMMARY:#{escape_text(event.title)}"
104
146
  ]
105
147
  add_optional_fields(vevent)
@@ -125,7 +167,8 @@ module CalInvite
125
167
  end
126
168
 
127
169
  if url = format_url
128
- vevent << "URL:#{escape_text(url)}"
170
+ # URL is a URI value, not TEXT: RFC 5545 escaping would corrupt it.
171
+ vevent << "URL:#{url}"
129
172
  end
130
173
 
131
174
  vevent << geo_line if geo_line
@@ -137,7 +180,7 @@ module CalInvite
137
180
  attendees_list.each { |attendee| vevent << attendee_line(attendee) }
138
181
 
139
182
  vevent << rrule_line if rrule_line
140
- vevent << "SEQUENCE:#{event.sequence}"
183
+ vevent << sequence_line
141
184
  vevent << status_line
142
185
  vevent << transp_line
143
186
  vevent << busystatus_line
@@ -163,6 +206,20 @@ module CalInvite
163
206
  time.strftime("%Y%m%d")
164
207
  end
165
208
 
209
+ # Builds a DTSTART/DTEND line: a `Z`-suffixed UTC timestamp for UTC events,
210
+ # otherwise local wall-clock time referencing the event's VTIMEZONE.
211
+ #
212
+ # @param name [String] The property name ("DTSTART" or "DTEND")
213
+ # @param time [Time] The time to format (UTC)
214
+ # @return [String] The content line
215
+ def date_time_line(name, time)
216
+ if CalInvite::IcalTimezone.utc?(event.timezone)
217
+ "#{name}:#{format_timestamp(time)}"
218
+ else
219
+ "#{name};TZID=#{event.timezone}:#{format_local_timestamp(time)}"
220
+ end
221
+ end
222
+
166
223
  # Formats a time object as a local timestamp in iCalendar format.
167
224
  # Converts from UTC to the event's timezone; times are expected to be in
168
225
  # UTC already.
@@ -173,17 +230,15 @@ module CalInvite
173
230
  local_wall_time(time).strftime("%Y%m%dT%H%M%S")
174
231
  end
175
232
 
176
- # Escapes special characters in text according to iCalendar spec.
233
+ # Escapes a TEXT value per RFC 5545 §3.3.11: backslash, semicolon and comma
234
+ # are backslash-escaped, and any line break (CRLF, CR or LF) becomes `\n`.
177
235
  #
178
236
  # @param text [String, nil] The text to escape
179
237
  # @return [String] The escaped text, or empty string if input was nil
180
238
  def escape_text(text)
181
239
  return '' if text.nil?
182
- text.to_s
183
- .gsub('\\', '\\\\')
184
- .gsub("\n", '\\n')
185
- .gsub(',', '\\,')
186
- .gsub(';', '\\;')
240
+
241
+ text.to_s.gsub(/\r\n|[\r\n\\;,]/) { |match| match.match?(/[\r\n]/) ? '\\n' : "\\#{match}" }
187
242
  end
188
243
  end
189
244
  end
@@ -3,142 +3,20 @@
3
3
  # lib/cal_invite/providers/ics_content.rb
4
4
  module CalInvite
5
5
  module Providers
6
- # ICS content provider for generating calendar files in iCalendar format.
7
- # This provider focuses on generating standards-compliant ICS content
8
- # that can be used directly or wrapped for file download.
6
+ # ICS content provider, for generating `.ics` content directly (outside
7
+ # Event#generate_calendar_url). Produces the same RFC 5545 content as {Ics};
8
+ # kept for backward compatibility. Pair with {IcsDownload} to serve it as a file.
9
9
  #
10
- # @example Generate ICS content for a single event
10
+ # @example
11
11
  # event = CalInvite::Event.new(
12
12
  # title: "Team Meeting",
13
13
  # start_time: Time.now,
14
14
  # end_time: Time.now + 3600
15
15
  # )
16
- # generator = CalInvite::Providers::IcsContent.new(event)
17
- # ics_content = generator.generate
18
- #
19
- # @example Generate ICS content for a multi-day event
20
- # event = CalInvite::Event.new(
21
- # title: "Conference",
22
- # multi_day_sessions: [
23
- # { start_time: Time.parse("2024-04-01 09:00"), end_time: Time.parse("2024-04-01 17:00") },
24
- # { start_time: Time.parse("2024-04-02 09:00"), end_time: Time.parse("2024-04-02 17:00") }
25
- # ]
26
- # )
27
16
  # ics_content = CalInvite::Providers::IcsContent.new(event).generate
28
- class IcsContent < BaseProvider
29
- # Generates the complete ICS calendar content with all event details.
30
- # Handles both single events and multi-day sessions.
31
- #
32
- # @return [String] The complete ICS calendar content in iCalendar format
33
- def generate
34
- calendar_lines = [
35
- "BEGIN:VCALENDAR",
36
- "VERSION:2.0",
37
- "PRODID:-//CalInvite//EN",
38
- "CALSCALE:GREGORIAN",
39
- "METHOD:#{method_value}"
40
- ]
41
-
42
- calendar_lines << "X-WR-CALNAME:#{escape_text(event.calendar_name)}" if event.calendar_name
43
-
44
- if event.multi_day_sessions.any?
45
- event.multi_day_sessions.each do |session|
46
- calendar_lines.concat(generate_vevent(session[:start_time], session[:end_time]))
47
- end
48
- else
49
- calendar_lines.concat(generate_vevent(event.start_time, event.end_time))
50
- end
51
-
52
- calendar_lines << "END:VCALENDAR"
53
- calendar_lines.join("\r\n")
54
- end
55
-
56
- private
57
-
58
- # Generates a single VEVENT component with proper event properties.
59
- #
60
- # @param start_time [Time] The event start time
61
- # @param end_time [Time] The event end time
62
- # @return [Array<String>] Array of iCalendar format lines for the event
63
- def generate_vevent(start_time, end_time)
64
- [
65
- "BEGIN:VEVENT",
66
- "UID:#{event.uid}",
67
- "DTSTAMP:#{format_timestamp(Time.now.utc)}",
68
- "DTSTART:#{format_timestamp(start_time)}",
69
- "DTEND:#{format_timestamp(end_time)}",
70
- "SUMMARY:#{escape_text(event.title)}",
71
- description_line,
72
- location_line,
73
- url_line,
74
- geo_line,
75
- organizer_line,
76
- attendee_lines,
77
- rrule_line,
78
- "SEQUENCE:#{event.sequence}",
79
- status_line,
80
- transp_line,
81
- busystatus_line,
82
- class_line,
83
- importance_lines,
84
- disallow_counter_line,
85
- valarm_lines,
86
- "END:VEVENT"
87
- ].compact
88
- end
89
-
90
- # Formats a time object as an UTC timestamp in iCalendar format.
91
- #
92
- # @param time [Time] The time to format
93
- # @return [String] The formatted UTC timestamp (YYYYMMDDTHHmmSSZ)
94
- def format_timestamp(time)
95
- time.utc.strftime("%Y%m%dT%H%M%SZ")
96
- end
97
-
98
- # Escapes special characters in text according to iCalendar spec.
99
- #
100
- # @param text [String, nil] The text to escape
101
- # @return [String] The escaped text, or empty string if input was nil
102
- def escape_text(text)
103
- return '' if text.nil?
104
- text.to_s
105
- .gsub('\\', '\\\\')
106
- .gsub("\n", '\\n')
107
- .gsub(',', '\\,')
108
- .gsub(';', '\\;')
109
- end
110
-
111
- # Generates the DESCRIPTION line if a description exists.
112
- #
113
- # @return [String, nil] The formatted DESCRIPTION line, or nil if no description
114
- def description_line
115
- return nil unless event.description
116
- "DESCRIPTION:#{escape_text(event.description)}"
117
- end
118
-
119
- # Generates the LOCATION line if a location exists.
120
- #
121
- # @return [String, nil] The formatted LOCATION line, or nil if no location
122
- def location_line
123
- return nil unless event.location
124
- "LOCATION:#{escape_text(event.location)}"
125
- end
126
-
127
- # Generates the URL line if a URL exists.
128
- #
129
- # @return [String, nil] The formatted URL line, or nil if no URL
130
- def url_line
131
- return nil unless event.url
132
- "URL:#{escape_text(event.url)}"
133
- end
134
-
135
- # Generates ATTENDEE lines for all attendees if showing attendees is enabled.
136
- #
137
- # @return [Array<String>, nil] Array of ATTENDEE lines, or nil if no attendees or not showing
138
- def attendee_lines
139
- return nil if attendees_list.empty?
140
- attendees_list.map { |attendee| attendee_line(attendee) }
141
- end
17
+ #
18
+ # @see Ics
19
+ class IcsContent < Ics
142
20
  end
143
21
 
144
22
  # Module for handling ICS file downloads with proper headers and file naming.
@@ -52,6 +52,7 @@ module CalInvite
52
52
  params = {
53
53
  subject: url_encode(event.title),
54
54
  path: '/calendar/action/compose',
55
+ rru: 'addevent',
55
56
  allday: 'true'
56
57
  }
57
58
 
@@ -73,7 +74,8 @@ module CalInvite
73
74
  def generate_single_event
74
75
  params = {
75
76
  subject: url_encode(event.title),
76
- path: '/calendar/action/compose'
77
+ path: '/calendar/action/compose',
78
+ rru: 'addevent'
77
79
  }
78
80
 
79
81
  raise ArgumentError, "Start time is required" unless event.start_time
@@ -119,6 +121,8 @@ module CalInvite
119
121
  params[:to] = url_encode(attendee_emails.join(';'))
120
122
  end
121
123
 
124
+ params[:freebusy] = event.busy ? 'busy' : 'free' unless event.busy.nil?
125
+
122
126
  params
123
127
  end
124
128
 
@@ -128,7 +132,7 @@ module CalInvite
128
132
  # @return [String] The complete Office 365 calendar URL
129
133
  def build_url(params)
130
134
  query = params.map { |k, v| "#{k}=#{v}" }.join('&')
131
- "https://outlook.office.com/owa/?#{query}"
135
+ "https://outlook.office.com/calendar/deeplink/compose?#{query}"
132
136
  end
133
137
  end
134
138
  end
@@ -55,7 +55,8 @@ module CalInvite
55
55
  # @return [String] The Outlook.com calendar URL for an all-day event
56
56
  def generate_all_day_event
57
57
  params = {
58
- path: '/calendar/0/action/compose',
58
+ path: '/calendar/action/compose',
59
+ rru: 'addevent',
59
60
  subject: url_encode(event.title),
60
61
  allday: 'true'
61
62
  }
@@ -77,7 +78,8 @@ module CalInvite
77
78
  # @raise [ArgumentError] If start_time or end_time is missing
78
79
  def generate_single_event
79
80
  params = {
80
- path: '/calendar/0/action/compose',
81
+ path: '/calendar/action/compose',
82
+ rru: 'addevent',
81
83
  subject: url_encode(event.title)
82
84
  }
83
85
 
@@ -124,6 +126,8 @@ module CalInvite
124
126
  params[:to] = url_encode(attendee_emails.join(';'))
125
127
  end
126
128
 
129
+ params[:freebusy] = event.busy ? 'busy' : 'free' unless event.busy.nil?
130
+
127
131
  params
128
132
  end
129
133
 
@@ -133,7 +137,7 @@ module CalInvite
133
137
  # @return [String] The complete Outlook.com calendar URL
134
138
  def build_url(params)
135
139
  query = params.map { |k, v| "#{k}=#{v}" }.join('&')
136
- "https://outlook.live.com/calendar/0/action/compose?#{query}"
140
+ "https://outlook.live.com/calendar/deeplink/compose?#{query}"
137
141
  end
138
142
  end
139
143
  end
@@ -22,3 +22,11 @@ module CalInvite
22
22
  autoload :Ics, 'cal_invite/providers/ics'
23
23
  end
24
24
  end
25
+
26
+ # Deprecated top-level alias, kept for one release so code that referenced
27
+ # ::BaseProvider keeps working. Skipped if the host app defines its own.
28
+ # Remove in 0.4.0.
29
+ unless Object.const_defined?(:BaseProvider, false)
30
+ BaseProvider = CalInvite::Providers::BaseProvider
31
+ Object.deprecate_constant(:BaseProvider)
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CalInvite
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/cal_invite.rb CHANGED
@@ -3,14 +3,12 @@ require 'logger'
3
3
  require 'mutex_m'
4
4
  require 'bigdecimal'
5
5
  require 'active_support'
6
- require 'active_support/core_ext'
7
6
  require 'securerandom'
8
7
  require 'time'
9
8
  require 'uri'
10
9
 
11
10
  require 'cal_invite/version'
12
11
  require 'cal_invite/configuration'
13
- require 'cal_invite/caching'
14
12
  require 'cal_invite/ical_timezone'
15
13
  require 'cal_invite/event'
16
14
  require 'cal_invite/providers'
@@ -21,7 +19,6 @@ require 'cal_invite/providers'
21
19
  # @example Configure the gem
22
20
  # CalInvite.configure do |config|
23
21
  # config.timezone = 'America/New_York'
24
- # config.cache_store = :memory_store
25
22
  # end
26
23
  #
27
24
  # @example Create and generate a calendar URL
@@ -49,8 +46,7 @@ module CalInvite
49
46
  # @example
50
47
  # CalInvite.configure do |config|
51
48
  # config.timezone = 'UTC'
52
- # config.cache_store = :memory_store
53
- # end
49
+ # end
54
50
  def configure
55
51
  self.configuration ||= Configuration.new
56
52
  yield(configuration)
@@ -62,8 +58,5 @@ module CalInvite
62
58
  def reset_configuration!
63
59
  self.configuration = Configuration.new
64
60
  end
65
-
66
- # Include caching methods at the module level
67
- include Caching
68
61
  end
69
62
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cal-invite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephane Paquet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-10 00:00:00.000000000 Z
11
+ date: 2026-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -102,26 +102,21 @@ executables: []
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - ".DS_Store"
106
105
  - ".rdoc_options"
107
106
  - ".rubocop.yml"
108
107
  - AGENTS.md
109
- - CACHING.md
110
108
  - CHANGELOG.md
111
- - CLAUDE.md
112
109
  - CODE_OF_CONDUCT.md
113
110
  - CONFIGURATION.md
114
111
  - LICENSE.txt
115
112
  - README.md
116
113
  - Rakefile
117
114
  - SECURITY.md
115
+ - assets/social/cal-invite-github.png
118
116
  - gemfiles/Gemfile.rails6
119
117
  - gemfiles/Gemfile.rails7
120
118
  - gemfiles/Gemfile.rails8
121
- - lib/.DS_Store
122
119
  - lib/cal_invite.rb
123
- - lib/cal_invite/.DS_Store
124
- - lib/cal_invite/caching.rb
125
120
  - lib/cal_invite/configuration.rb
126
121
  - lib/cal_invite/event.rb
127
122
  - lib/cal_invite/ical_timezone.rb
data/.DS_Store DELETED
Binary file