cal-invite 0.3.0 → 0.3.2
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/AGENTS.md +1 -1
- data/CACHING.md +240 -0
- data/CHANGELOG.md +23 -2
- data/CONFIGURATION.md +1 -1
- data/README.md +133 -203
- data/Rakefile +3 -1
- data/lib/cal_invite/version.rb +1 -1
- metadata +4 -4
- data/assets/social/cal-invite-github.png +0 -0
- data/lib/tasks/rdoc.rake +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 859781c1ae62094d375450f6a4ce69578eeae805a9986fca889927cdd49f446c
|
|
4
|
+
data.tar.gz: aed11a758d0b1a9d4eabf09e56609c9f1bd800b85503a5e171c7c9858f03c6dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee1118ede1a8f029d66d59d22615d95eaf17ffcc9290fe82a3d59e98808d4f1b5de58df375c88ebc1f4d9f62228437632a1724fceccb28b039f8002174e51f94
|
|
7
|
+
data.tar.gz: 259efb6d6829b774aeb6d2d35e0c1518c00831cd81340ff519b3a7f4b7e726b0fb881f586bcf20fe98a89f4a46e4057e70aafc6dc790f28f213f9afa75f96f43
|
data/AGENTS.md
CHANGED
|
@@ -45,7 +45,7 @@ Tests are Minitest-based, files live under `test/cal_invite/`, named `*_test.rb`
|
|
|
45
45
|
|
|
46
46
|
## Example app (`Example/calendar_app`)
|
|
47
47
|
|
|
48
|
-
Rails 8.1 app demonstrating the gem. Its Gemfile depends on the **published** `cal-invite` gem (never commit it pointing at `path: "../.."`) — when testing example-app changes that rely on unreleased gem code, temporarily uncomment the `path: "../.."` line locally, `bundle install`, test, then revert the Gemfile and re-run `bundle install` against rubygems before committing. `Meeting`/`MeetingAttendee` (with a migration) is a working reference implementation of the `uid`/`sequence` persistence pattern CONFIGURATION.md describes — see `app/models/meeting.rb`, `app/controllers/meetings_controller.rb` (create/reschedule/cancel), and `app/controllers/event_replies_controller.rb` (illustrative inbound `METHOD:REPLY` parsing, not wired to a real inbound-email provider).
|
|
48
|
+
Rails 8.1 app demonstrating the gem. Its Gemfile depends on the **published** `cal-invite` gem (never commit it pointing at `path: "../.."`) — when testing example-app changes that rely on unreleased gem code, temporarily uncomment the `path: "../.."` line locally, `bundle install`, test, then revert the Gemfile and re-run `bundle install` against rubygems before committing. `Meeting`/`MeetingAttendee` (with a migration) is a working reference implementation of the `uid`/`sequence` persistence pattern CONFIGURATION.md describes — see `app/models/meeting.rb`, `app/controllers/meetings_controller.rb` (create/reschedule/cancel, plus `show` serving a cached `.ics` with `stale?` + `Rails.cache` per CACHING.md), and `app/controllers/event_replies_controller.rb` (illustrative inbound `METHOD:REPLY` parsing, not wired to a real inbound-email provider).
|
|
49
49
|
|
|
50
50
|
## PRs & commits
|
|
51
51
|
|
data/CACHING.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Caching CalInvite output in your application
|
|
2
|
+
|
|
3
|
+
CalInvite doesn't cache anything (see the [0.3.0 changelog](CHANGELOG.md)). Building a URL or `.ics` file is in-memory string work that takes microseconds, and the gem can't know when your data changes. Your application does, so caching belongs there.
|
|
4
|
+
|
|
5
|
+
This guide shows a few ways to do it in plain Ruby, Rails, Sinatra, Hanami and Grape. It isn't exhaustive. Every example was run against the framework version named in its section.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Do you need a cache?](#do-you-need-a-cache)
|
|
10
|
+
- [Four rules for correct caching](#four-rules-for-correct-caching)
|
|
11
|
+
- [The example model](#the-example-model)
|
|
12
|
+
- [Plain Ruby](#plain-ruby)
|
|
13
|
+
- [Rails 8.1](#rails-81)
|
|
14
|
+
- [Sinatra](#sinatra)
|
|
15
|
+
- [Hanami](#hanami)
|
|
16
|
+
- [Grape](#grape)
|
|
17
|
+
|
|
18
|
+
## Do you need a cache?
|
|
19
|
+
|
|
20
|
+
Usually not for the output itself. Caching it pays off in two cases:
|
|
21
|
+
|
|
22
|
+
- **Repeated downloads of the same `.ics`.** Calendar apps that subscribe to a feed poll it every few minutes to hours. HTTP caching (`ETag`/`Last-Modified` and a `304 Not Modified` reply) saves the bandwidth and lets the client skip re-importing an unchanged file. This is the most useful kind of caching for calendar files.
|
|
23
|
+
- **Expensive work before the gem runs.** If building the `Event` means loading a meeting, its attendees and its venue from the database, cache the result to skip those queries. CalInvite's own share of the time is negligible.
|
|
24
|
+
|
|
25
|
+
Don't cache invites you send by email (`method: :request`, `:cancel`, ...). Each send should be generated when it's sent; see rule 3.
|
|
26
|
+
|
|
27
|
+
## Four rules for correct caching
|
|
28
|
+
|
|
29
|
+
1. **Key on the data's version, not on the output.** The key needs the record's identity, something that changes whenever the record changes (`updated_at`, a version column, the invite `sequence`), and every input that changes the output: provider, `method:`, and anything that varies per viewer, such as whether attendees are shown.
|
|
30
|
+
2. **Persist the event's `uid`.** Without an explicit `uid:`, each `Event` gets a random one, so two builds of the same meeting produce different `.ics` content. Store the `uid` on your record and pass it in.
|
|
31
|
+
3. **Never build an `ETag` by hashing the `.ics` content.** Every generation writes the current time into `DTSTAMP`, so the hash changes on every request and the client never gets a `304`. Derive the `ETag` from the record's version. A cached `.ics` also keeps the `DTSTAMP` from when it was cached. That's harmless for downloads and feeds, but it's another reason to generate emailed invites fresh.
|
|
32
|
+
4. **Include the configured timezone if it can change at runtime.** `CalInvite.configuration.timezone` is the default `timezone` for events. Most apps set it once at boot, so it doesn't need to be in the key.
|
|
33
|
+
|
|
34
|
+
## The example model
|
|
35
|
+
|
|
36
|
+
The plain Ruby, Sinatra, Hanami and Grape examples use this stand-in for a database record. In Rails it's an Active Record model with the same `to_cal_invite_event` method.
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "cal_invite"
|
|
40
|
+
|
|
41
|
+
Meeting = Struct.new(:id, :title, :starts_at, :ends_at, :uid, :sequence, :updated_at, keyword_init: true) do
|
|
42
|
+
def to_cal_invite_event
|
|
43
|
+
CalInvite::Event.new(title: title, start_time: starts_at, end_time: ends_at, uid: uid, sequence: sequence)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Changes whenever the meeting changes (rule 1).
|
|
47
|
+
def cache_version
|
|
48
|
+
"#{id}-#{updated_at.to_i}-#{sequence}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
MEETINGS = {
|
|
53
|
+
1 => Meeting.new(id: 1, title: "Design review", uid: "meeting-1@example.com", sequence: 0,
|
|
54
|
+
starts_at: Time.utc(2026, 11, 2, 15), ends_at: Time.utc(2026, 11, 2, 16),
|
|
55
|
+
updated_at: Time.utc(2026, 10, 20, 9))
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Plain Ruby
|
|
60
|
+
|
|
61
|
+
Written for Ruby 3.4 and run on Ruby 4.0. Ruby 4.0 doesn't add anything that changes these patterns; see [Ruby 4.0](#ruby-40) below.
|
|
62
|
+
|
|
63
|
+
### A small in-process LRU cache
|
|
64
|
+
|
|
65
|
+
A `Hash` keeps insertion order, so deleting and re-inserting a key on each hit makes the first key the least recently used one. A `Mutex` keeps it safe under threaded servers like Puma.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class IcsCache
|
|
69
|
+
def initialize(max_entries: 1_000)
|
|
70
|
+
@max_entries = max_entries
|
|
71
|
+
@entries = Hash.new(capacity: max_entries) # Ruby 3.4+: allocate the table once
|
|
72
|
+
@lock = Mutex.new
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fetch(key)
|
|
76
|
+
@lock.synchronize do
|
|
77
|
+
return @entries[key] = @entries.delete(key) if @entries.key?(key) # move to most recent
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
value = yield # generate outside the lock
|
|
81
|
+
@lock.synchronize do
|
|
82
|
+
@entries[key] = value
|
|
83
|
+
@entries.shift while @entries.size > @max_entries # evict least recently used
|
|
84
|
+
end
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ICS_CACHE = IcsCache.new
|
|
90
|
+
|
|
91
|
+
def meeting_ics(meeting)
|
|
92
|
+
ICS_CACHE.fetch([meeting.cache_version, :ics, :publish]) do
|
|
93
|
+
meeting.to_cal_invite_event.generate_calendar_url(:ics)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When a meeting changes, its `cache_version` changes, so the next call misses and the old entry ages out. Nothing has to be deleted by hand.
|
|
99
|
+
|
|
100
|
+
Each server process has its own copy of this cache. That's fine for a single process or a small app. If several processes or machines should share one cache, use a shared store such as Redis.
|
|
101
|
+
|
|
102
|
+
### ActiveSupport cache stores
|
|
103
|
+
|
|
104
|
+
CalInvite already depends on `activesupport`, so its cache stores are available without Rails. `MemoryStore` adds a size limit and expiry to the pattern above, and the same `fetch` call works with `RedisCacheStore` (add the `redis` gem) or `FileStore` when processes need to share a cache.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
require "active_support"
|
|
108
|
+
|
|
109
|
+
STORE = ActiveSupport::Cache::MemoryStore.new(size: 32 * 1024 * 1024, expires_in: 3600)
|
|
110
|
+
|
|
111
|
+
def meeting_ics_from_store(meeting)
|
|
112
|
+
STORE.fetch(["meeting-ics", meeting.cache_version]) do
|
|
113
|
+
meeting.to_cal_invite_event.generate_calendar_url(:ics)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
On ActiveSupport 7.x and earlier, loading the cache stores also loads some of ActiveSupport's core extensions. ActiveSupport 8 doesn't.
|
|
119
|
+
|
|
120
|
+
### Ruby 4.0
|
|
121
|
+
|
|
122
|
+
Nothing in Ruby 4.0 improves this kind of caching. The headline changes are the `Ractor::Port` API, `Set` becoming a core class, and the experimental `Ruby::Box` isolation. The `Mutex`-guarded `Hash` above can't be shared between Ractors, but web servers and frameworks run requests on threads or processes rather than Ractors, so this doesn't affect normal applications. Both examples above run unchanged on 4.0.
|
|
123
|
+
|
|
124
|
+
## Rails 8.1
|
|
125
|
+
|
|
126
|
+
Tested with Rails 8.1. `Rails.cache` is the cache store (Solid Cache by default since Rails 8.0), and `stale?` handles conditional GET: it sets `ETag` and `Last-Modified` from the record and answers `304 Not Modified` when the client's copy is current. Rails already knows the `text/calendar` MIME type as `:ics`.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# config/routes.rb
|
|
130
|
+
resources :meetings, only: :show
|
|
131
|
+
|
|
132
|
+
# app/models/meeting.rb
|
|
133
|
+
class Meeting < ApplicationRecord
|
|
134
|
+
def to_cal_invite_event
|
|
135
|
+
CalInvite::Event.new(title: title, start_time: starts_at, end_time: ends_at, uid: uid, sequence: sequence)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# app/controllers/meetings_controller.rb
|
|
140
|
+
class MeetingsController < ApplicationController
|
|
141
|
+
def show
|
|
142
|
+
meeting = Meeting.find(params[:id])
|
|
143
|
+
|
|
144
|
+
respond_to do |format|
|
|
145
|
+
format.ics do
|
|
146
|
+
# ETag from meeting.cache_key_with_version, Last-Modified from updated_at (rule 3)
|
|
147
|
+
if stale?(meeting, public: true)
|
|
148
|
+
ics = Rails.cache.fetch([meeting, :ics, :publish]) do
|
|
149
|
+
meeting.to_cal_invite_event.generate_calendar_url(:ics)
|
|
150
|
+
end
|
|
151
|
+
send_data ics, type: :ics, disposition: "inline", filename: "meeting-#{meeting.id}.ics"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`GET /meetings/1.ics` returns the file, and a repeat request with the `ETag` it got back returns `304`. Passing `meeting` in the cache key uses its `cache_key_with_version`, which changes with `updated_at`, so updating the record (including bumping `sequence`) also refreshes the cached copy. If `sequence` lives somewhere that doesn't touch `updated_at`, add it to both the key and `stale?(etag: ...)`.
|
|
160
|
+
|
|
161
|
+
The [example app](Example/calendar_app) has a complete version in `MeetingsController#show`, with tests. Its meetings list attendees, so it keeps `Cache-Control` private (no `public: true`), uses `belongs_to :meeting, touch: true` so an attendee's RSVP change expires the cache, and bumps `sequence` with `increment!(:sequence, touch: true)`, because `increment!` alone doesn't update `updated_at`.
|
|
162
|
+
|
|
163
|
+
## Sinatra
|
|
164
|
+
|
|
165
|
+
Tested with Sinatra 4.2. The `etag` and `last_modified` helpers stop the route with a `304` when the client's copy is current, so the `.ics` is only generated, or read from the cache, for clients that need it.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
require "sinatra/base"
|
|
169
|
+
|
|
170
|
+
class CalendarApp < Sinatra::Base
|
|
171
|
+
get "/meetings/:id.ics" do
|
|
172
|
+
meeting = MEETINGS.fetch(params[:id].to_i) { halt 404 }
|
|
173
|
+
|
|
174
|
+
cache_control :public, max_age: 300
|
|
175
|
+
last_modified meeting.updated_at
|
|
176
|
+
etag meeting.cache_version # halts with 304 if If-None-Match matches
|
|
177
|
+
|
|
178
|
+
content_type "text/calendar", charset: "utf-8"
|
|
179
|
+
meeting_ics(meeting) # the IcsCache helper from the plain Ruby section
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Hanami
|
|
185
|
+
|
|
186
|
+
Tested with hanami-action 3.0, the action library in Hanami 2.3 and later. Register the `ics` format once in `config/app.rb` with `config.actions.formats.register(:ics, "text/calendar")`. Hanami 2.2 and earlier used `formats.add`, which was removed in hanami-action 3.0.
|
|
187
|
+
|
|
188
|
+
`response.fresh` sets the `ETag`/`Last-Modified` headers and halts with `304` when the request is still fresh, before the `.ics` is built.
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
module Calendar
|
|
192
|
+
module Actions
|
|
193
|
+
module Meetings
|
|
194
|
+
class Show < Calendar::Action
|
|
195
|
+
def handle(request, response)
|
|
196
|
+
meeting = MEETINGS.fetch(request.params[:id].to_i) { halt 404 }
|
|
197
|
+
|
|
198
|
+
response.cache_control :public, max_age: 300
|
|
199
|
+
response.fresh etag: meeting.cache_version, last_modified: meeting.updated_at
|
|
200
|
+
|
|
201
|
+
response.format = :ics
|
|
202
|
+
response.body = meeting_ics(meeting)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Grape
|
|
211
|
+
|
|
212
|
+
Tested with Grape 4.0. Grape has no conditional-GET helper, so the endpoint compares `If-None-Match` itself. Registering an `ics` content type and a pass-through formatter makes `GET /meetings/1.ics` return the `.ics` string as-is, without JSON-encoding it.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
require "grape"
|
|
216
|
+
|
|
217
|
+
class CalendarAPI < Grape::API
|
|
218
|
+
content_type :ics, "text/calendar"
|
|
219
|
+
formatter :ics, ->(object, _env) { object }
|
|
220
|
+
|
|
221
|
+
resource :meetings do
|
|
222
|
+
get ":id" do
|
|
223
|
+
meeting = MEETINGS.fetch(params[:id].to_i) { error!("Not found", 404) }
|
|
224
|
+
etag = %("#{meeting.cache_version}")
|
|
225
|
+
|
|
226
|
+
header "ETag", etag
|
|
227
|
+
header "Cache-Control", "public, max-age=300"
|
|
228
|
+
if env["HTTP_IF_NONE_MATCH"] == etag
|
|
229
|
+
status 304
|
|
230
|
+
return ""
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
content_type "text/calendar; charset=utf-8"
|
|
234
|
+
meeting_ics(meeting)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The same API can still serve JSON from other endpoints. The `.ics` extension selects the formatter.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Cal Invite
|
|
2
2
|
|
|
3
|
+
## [v0.3.2] - 2026-10-22
|
|
4
|
+
|
|
5
|
+
No changes to the library code; this release is documentation and CI.
|
|
6
|
+
|
|
7
|
+
- **API docs layout fixed.** The README's wide banner image ran under the "On This Page" sidebar on the published docs. RDoc now loads `assets/rdoc-overrides.css`, which keeps images inside the content column.
|
|
8
|
+
- **Docs deploy on their own.** The documentation workflow runs on every push to `main` that changes a file the docs render, and can be started by hand from the Actions tab on any branch. Release tags still publish the docs as before.
|
|
9
|
+
- **Faster CI for doc-only changes.** Changes that touch only Markdown files or `assets/` skip the test matrix. Tag pushes always run the full pipeline.
|
|
10
|
+
- **Docs checked on pull requests.** A PR that changes a file the docs render now runs the RDoc build, so a broken build fails before merge. Nothing is deployed from a PR.
|
|
11
|
+
- **Workflow lint and wider test matrix.** `actionlint` runs on the workflows, and the test matrix adds Ruby 3.4.
|
|
12
|
+
- **CI tidy-ups.** Superseded pull request runs are cancelled. Documentation deploys queue instead of cancelling each other. Workflow permissions are narrowed, and the test job has a 20-minute timeout.
|
|
13
|
+
|
|
14
|
+
## [v0.3.1] - 2026-10-22
|
|
15
|
+
|
|
16
|
+
No changes to the library code; this release is packaging and documentation.
|
|
17
|
+
|
|
18
|
+
- **Smaller gem.** The package no longer includes `assets/` (README images) or the unused `lib/tasks/rdoc.rake`: 52 KB instead of 973 KB.
|
|
19
|
+
- **API reference fixed.** The published docs at https://dashapi-ai.github.io/cal-invite/ were missing `lib/cal_invite.rb` and every provider class (`Google`, `Outlook`, `Office365`, `Yahoo`, `Ics`, `Ical`, `IcsContent`, `IcsDownload`, `BaseProvider`), because the docs workflow's `lib/**/*.rb` glob only matched one directory level. The workflow now runs `rake rdoc` and also publishes the README, CONFIGURATION.md, CACHING.md and CHANGELOG.md.
|
|
20
|
+
- **New CACHING.md** on caching CalInvite output in your app (plain Ruby, Rails 8.1, Sinatra, Hanami, Grape), including HTTP caching for `.ics` feeds. The example app gains a cached `.ics` download (`MeetingsController#show`).
|
|
21
|
+
- **README rewritten** for 0.3.0, with accurate examples that run in plain Ruby.
|
|
22
|
+
- **Gem metadata:** the RubyGems changelog link pointed at a non-existent `master` branch; it now points at `main`, and a documentation link was added.
|
|
23
|
+
|
|
3
24
|
## [v0.3.0] - 2026-10-22
|
|
4
25
|
|
|
5
26
|
### Upgrading from 0.2.x
|
|
@@ -105,7 +126,7 @@ These changes can break existing callers:
|
|
|
105
126
|
|
|
106
127
|
## [v0.1.2] - 2024-12-17
|
|
107
128
|
|
|
108
|
-
|
|
129
|
+
**Warning:** This version should not be used
|
|
109
130
|
|
|
110
131
|
- Add support to Microsoft office 365 calendar invite URL
|
|
111
132
|
- Update the README
|
|
@@ -114,7 +135,7 @@ These changes can break existing callers:
|
|
|
114
135
|
|
|
115
136
|
## [v0.1.1] - 2024-12-17
|
|
116
137
|
|
|
117
|
-
|
|
138
|
+
**Warning:** This version should not be used
|
|
118
139
|
|
|
119
140
|
Fixing a bug in the gemspec file
|
|
120
141
|
|
data/CONFIGURATION.md
CHANGED
|
@@ -99,7 +99,7 @@ event.generate_calendar_url(provider, method: :publish)
|
|
|
99
99
|
| `provider`| `Symbol` | required | One of the provider symbols above (a String also works). Anything else raises `ArgumentError` listing the supported providers. `:ics_content` is accepted as a legacy alias of `:ics`. |
|
|
100
100
|
| `method` | `Symbol` | `:publish` | `:publish`, `:request`, `:cancel`, `:reply`, `:counter`, or `:decline_counter`. Only honored by `:ics`/`:ical` — ignored by URL-based providers. `:request` requires `organizer` to be set on the event; see below. `:cancel` requires reusing the original `uid` — see [Updating and cancelling invites](#updating-and-cancelling-invites). `:reply`/`:counter`/`:decline_counter` omit `RSVP=TRUE` on `ATTENDEE` lines — see [Attendee RSVP status and METHOD:REPLY](#attendee-rsvp-status-and-methodreply) and [Attendee-proposed reschedules (COUNTER)](#attendee-proposed-reschedules-counter). Note: `:decline_counter` renders as `METHOD:DECLINECOUNTER` (one word, per RFC 5545). |
|
|
101
101
|
|
|
102
|
-
Output is generated fresh on every call. CalInvite doesn't cache anything: building the output takes microseconds, and a cached `.ics` would keep a stale `DTSTAMP`. If you want caching anyway, do it in your app, keyed on whatever identifies the content and changes with it:
|
|
102
|
+
Output is generated fresh on every call. CalInvite doesn't cache anything: building the output takes microseconds, and a cached `.ics` would keep a stale `DTSTAMP`. If you want caching anyway, do it in your app, keyed on whatever identifies the content and changes with it (more examples, including HTTP caching, in [CACHING.md](CACHING.md)):
|
|
103
103
|
|
|
104
104
|
```ruby
|
|
105
105
|
Rails.cache.fetch([meeting, meeting.sequence, :ics]) do
|
data/README.md
CHANGED
|
@@ -1,280 +1,210 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CalInvite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
5
|
+
Generate "add to calendar" links and `.ics` calendar invites from Ruby, for Google Calendar, Outlook, Outlook 365, Yahoo, Apple Calendar, and any client that reads iCalendar files.
|
|
7
6
|
|
|
7
|
+
[](https://rubygems.org/gems/cal-invite)
|
|
8
|
+

|
|
8
9
|

|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- Ruby >= 3.0.0
|
|
13
|
-
- Rails 6.0, 6.1, 7.0, 7.1, 8.0, 8.1
|
|
14
|
-
|
|
15
|
-
## Supported Calendar Platforms
|
|
11
|
+
You describe an event once and get a link or `.ics` content for each provider. With `method: :request`, the `.ics` is an RFC 5545 meeting request that Gmail, Outlook and Apple Mail show with Accept/Decline buttons, and later updates or cancellations replace the original invite. Any IANA timezone gets a full `VTIMEZONE` with correctly converted times. Attribute values are escaped, so end-user input can't add properties to an invite.
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
- Apple iCal (with proper timezone support)
|
|
19
|
-
- Microsoft Outlook
|
|
20
|
-
- Microsoft Outlook 365
|
|
21
|
-
- Google Calendar
|
|
22
|
-
- Yahoo Calendar
|
|
23
|
-
- Standard .ics file generation
|
|
13
|
+
## Contents
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick start](#quick-start)
|
|
17
|
+
- [Providers](#providers)
|
|
18
|
+
- [More examples](#more-examples)
|
|
19
|
+
- [Serving a downloadable .ics file](#serving-a-downloadable-ics-file)
|
|
20
|
+
- [Sending email invites with RSVP](#sending-email-invites-with-rsvp)
|
|
21
|
+
- [Things to know](#things-to-know)
|
|
22
|
+
- [Upgrading to 0.3.0](#upgrading-to-030)
|
|
23
|
+
- [Documentation](#documentation)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Sponsor](#sponsor)
|
|
32
26
|
|
|
33
27
|
## Installation
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
CalInvite is published on [RubyGems](https://rubygems.org/gems/cal-invite). It requires Ruby 3.0 or newer. It works with or without Rails, and supports Rails 6.0 through 8.1.
|
|
36
30
|
|
|
37
31
|
```ruby
|
|
38
|
-
|
|
32
|
+
# Gemfile
|
|
33
|
+
gem "cal-invite"
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
And then execute:
|
|
42
36
|
```bash
|
|
43
|
-
|
|
37
|
+
bundle install
|
|
44
38
|
```
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require "cal_invite"
|
|
44
|
+
|
|
45
|
+
event = CalInvite::Event.new(
|
|
46
|
+
title: "Team Meeting",
|
|
47
|
+
start_time: Time.utc(2026, 11, 2, 15, 0), # always pass UTC
|
|
48
|
+
end_time: Time.utc(2026, 11, 2, 16, 0),
|
|
49
|
+
timezone: "America/New_York", # how the time is displayed
|
|
50
|
+
description: "Weekly sync",
|
|
51
|
+
location: "Conference Room A", # physical place
|
|
52
|
+
url: "https://zoom.us/j/123456789" # virtual meeting link
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
event.generate_calendar_url(:google) # => "https://calendar.google.com/calendar/render?action=TEMPLATE&..."
|
|
56
|
+
event.generate_calendar_url(:ics) # => "BEGIN:VCALENDAR\r\n..." (.ics file content)
|
|
49
57
|
```
|
|
50
58
|
|
|
51
|
-
##
|
|
59
|
+
## Providers
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
| Provider | Returns | Notes |
|
|
62
|
+
|--------------|---------|-------|
|
|
63
|
+
| `:google` | URL | Opens Google Calendar's "add event" page. |
|
|
64
|
+
| `:outlook` | URL | Outlook.com (personal accounts). |
|
|
65
|
+
| `:office365` | URL | Outlook on the web for work/school (Microsoft 365) accounts. |
|
|
66
|
+
| `:yahoo` | URL | One URL per session for multi-day events, separated by newlines. |
|
|
67
|
+
| `:ics` | `.ics` content | Standard iCalendar file; works with Apple Calendar, Thunderbird, Proton, Fastmail and others. |
|
|
68
|
+
| `:ical` | `.ics` content | Same content as `:ics`; kept for Apple Calendar naming. |
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
- Always provide times in UTC
|
|
57
|
-
- Use the timezone parameter to specify the display timezone
|
|
58
|
-
- Location and URL are handled separately for better calendar integration
|
|
70
|
+
Only `:ics` and `:ical` support the `method:` option (`:publish` by default, also `:request`, `:cancel`, `:reply`, `:counter`, `:decline_counter`). Any other provider name raises `ArgumentError`.
|
|
59
71
|
|
|
60
|
-
|
|
72
|
+
## More examples
|
|
61
73
|
|
|
62
74
|
```ruby
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
title: "
|
|
66
|
-
start_time: Time.
|
|
67
|
-
|
|
68
|
-
description: "Weekly team sync",
|
|
69
|
-
location: "Conference Room A", # Physical location
|
|
70
|
-
timezone: "America/New_York", # Display timezone
|
|
71
|
-
attendees: ["person@example.com"],
|
|
72
|
-
show_attendees: true,
|
|
73
|
-
notes: "Please bring your laptop"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Create an event with both physical and virtual locations
|
|
77
|
-
event = CalInvite::Event.new(
|
|
78
|
-
title: "Hybrid Meeting",
|
|
79
|
-
start_time: Time.current.utc,
|
|
80
|
-
end_time: Time.current.utc + 2.hours,
|
|
81
|
-
description: "Weekly team sync",
|
|
82
|
-
location: "Conference Room A", # Physical location
|
|
83
|
-
url: "https://zoom.us/j/123456789", # Virtual meeting URL
|
|
84
|
-
timezone: "America/New_York",
|
|
85
|
-
attendees: ["person@example.com"],
|
|
75
|
+
# Attendees: email strings, or hashes with a display name and RSVP status
|
|
76
|
+
CalInvite::Event.new(
|
|
77
|
+
title: "Planning",
|
|
78
|
+
start_time: Time.utc(2026, 11, 2, 15), end_time: Time.utc(2026, 11, 2, 16),
|
|
79
|
+
attendees: ["bob@example.com", { email: "ada@example.com", name: "Ada", partstat: :accepted }],
|
|
86
80
|
show_attendees: true
|
|
87
81
|
)
|
|
88
82
|
|
|
89
83
|
# All-day event
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
CalInvite::Event.new(title: "Company Offsite", start_time: Time.utc(2026, 12, 4), all_day: true)
|
|
85
|
+
|
|
86
|
+
# Recurring event, with a reminder 15 minutes before
|
|
87
|
+
CalInvite::Event.new(
|
|
88
|
+
title: "Standup",
|
|
89
|
+
start_time: Time.utc(2026, 11, 2, 14), end_time: Time.utc(2026, 11, 2, 14, 15),
|
|
90
|
+
timezone: "Europe/Paris",
|
|
91
|
+
rrule: "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
|
|
92
|
+
reminders: [15]
|
|
96
93
|
)
|
|
97
94
|
|
|
98
|
-
# Multi-day event
|
|
99
|
-
|
|
95
|
+
# Multi-day event with separate sessions
|
|
96
|
+
CalInvite::Event.new(
|
|
100
97
|
title: "Training Workshop",
|
|
101
98
|
multi_day_sessions: [
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
end_time: Time.parse("2024-03-01 17:00:00 UTC")
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
start_time: Time.parse("2024-03-02 09:00:00 UTC"),
|
|
108
|
-
end_time: Time.parse("2024-03-02 13:00:00 UTC")
|
|
109
|
-
}
|
|
99
|
+
{ start_time: Time.utc(2026, 3, 1, 13), end_time: Time.utc(2026, 3, 1, 22) },
|
|
100
|
+
{ start_time: Time.utc(2026, 3, 2, 14), end_time: Time.utc(2026, 3, 2, 18) }
|
|
110
101
|
],
|
|
111
|
-
description: "Advanced Ruby Training",
|
|
112
|
-
location: "Training Center",
|
|
113
|
-
url: "https://zoom.us/j/123456789", # Virtual meeting URL kept separate
|
|
114
102
|
timezone: "America/New_York",
|
|
115
|
-
|
|
103
|
+
location: "Training Center"
|
|
116
104
|
)
|
|
117
|
-
|
|
118
|
-
# Generate calendar URLs
|
|
119
|
-
ical_url = event.generate_calendar_url(:ical)
|
|
120
|
-
google_url = event.generate_calendar_url(:google)
|
|
121
|
-
outlook_url = event.generate_calendar_url(:outlook)
|
|
122
|
-
outlook365_url = event.generate_calendar_url(:office365)
|
|
123
|
-
yahoo_url = event.generate_calendar_url(:yahoo)
|
|
124
105
|
```
|
|
125
106
|
|
|
126
|
-
|
|
107
|
+
For a multi-day event, `:ics`, `:ical` and `:yahoo` produce one entry per session. Google and Outlook can only show one range, so they show the whole span, from the first session's start to the last session's end.
|
|
127
108
|
|
|
128
|
-
|
|
109
|
+
Every attribute (`organizer`, `geo`, `busy`, `visibility`, `importance`, `calendar_name`, ...) is listed in [CONFIGURATION.md](CONFIGURATION.md#event-attributes).
|
|
129
110
|
|
|
130
|
-
|
|
131
|
-
2. Generate the ICS content
|
|
132
|
-
3. Send the file to the user
|
|
133
|
-
|
|
134
|
-
Here's a basic example of the controller logic:
|
|
111
|
+
## Serving a downloadable .ics file
|
|
135
112
|
|
|
136
113
|
```ruby
|
|
137
|
-
#
|
|
138
|
-
def
|
|
139
|
-
event =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
filename = "#{event.title.downcase.gsub(/[^0-9A-Za-z.\-]/, '_')}_#{Time.now.strftime('%Y%m%d')}.ics"
|
|
143
|
-
|
|
144
|
-
send_data(
|
|
145
|
-
content,
|
|
146
|
-
filename: filename,
|
|
147
|
-
type: 'text/calendar; charset=UTF-8',
|
|
148
|
-
disposition: 'attachment'
|
|
114
|
+
# app/controllers/events_controller.rb
|
|
115
|
+
def calendar
|
|
116
|
+
event = CalInvite::Event.new(...)
|
|
117
|
+
download = CalInvite::Providers::IcsDownload.wrap_for_download(
|
|
118
|
+
event.generate_calendar_url(:ics), event.title
|
|
149
119
|
)
|
|
120
|
+
|
|
121
|
+
send_data download[:content],
|
|
122
|
+
type: download[:headers]["Content-Type"],
|
|
123
|
+
filename: "#{CalInvite::Providers::IcsDownload.sanitize_filename(event.title)}.ics",
|
|
124
|
+
disposition: "attachment"
|
|
150
125
|
end
|
|
151
126
|
```
|
|
152
127
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
### Sending Email Meeting Invites (RSVP-capable)
|
|
156
|
-
|
|
157
|
-
By default, a `.ics` attachment opens as a file in most mail clients. To get
|
|
158
|
-
Gmail/Outlook/Apple Mail to render the message as an invite with Accept/Decline
|
|
159
|
-
actions (like Luma or Google Calendar invites do), set an `organizer` on the
|
|
160
|
-
event and pass `method: :request` when generating. Full walkthrough and all
|
|
161
|
-
options in [CONFIGURATION.md](CONFIGURATION.md#email-meeting-invites-rsvp-capable).
|
|
128
|
+
## Sending email invites with RSVP
|
|
162
129
|
|
|
163
|
-
|
|
130
|
+
A plain `.ics` attachment opens as a file. To have mail clients show it as a meeting invite with Accept/Decline buttons, you need three things: an `organizer`, `method: :request`, and a matching `method=REQUEST` on the attachment's content type.
|
|
164
131
|
|
|
165
|
-
The gem provides two ways to generate ICS files:
|
|
166
|
-
|
|
167
|
-
1. Direct content generation:
|
|
168
132
|
```ruby
|
|
169
133
|
event = CalInvite::Event.new(
|
|
170
|
-
title: "
|
|
171
|
-
start_time: Time.
|
|
172
|
-
|
|
173
|
-
|
|
134
|
+
title: "Design Review",
|
|
135
|
+
start_time: Time.utc(2026, 11, 2, 15), end_time: Time.utc(2026, 11, 2, 16),
|
|
136
|
+
organizer: { name: "Jane Doe", email: "jane@example.com" },
|
|
137
|
+
attendees: ["guest@example.com"],
|
|
138
|
+
show_attendees: true
|
|
174
139
|
)
|
|
175
140
|
|
|
176
|
-
#
|
|
177
|
-
|
|
141
|
+
# In your ActionMailer method:
|
|
142
|
+
attachments["invite.ics"] = {
|
|
143
|
+
mime_type: "text/calendar; method=REQUEST",
|
|
144
|
+
content: event.generate_calendar_url(:ics, method: :request)
|
|
145
|
+
}
|
|
178
146
|
```
|
|
179
147
|
|
|
180
|
-
|
|
181
|
-
```ruby
|
|
182
|
-
# In your controller
|
|
183
|
-
def download_ics
|
|
184
|
-
event = CalInvite::Event.new(
|
|
185
|
-
title: "Meeting",
|
|
186
|
-
start_time: Time.current.utc,
|
|
187
|
-
end_time: Time.current.utc + 1.hour,
|
|
188
|
-
timezone: "America/New_York"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
content = CalInvite::Providers::Ics.new(event).generate
|
|
192
|
-
filename = "#{event.title.downcase.gsub(/[^0-9A-Za-z.\-]/, '_')}_#{Time.now.strftime('%Y%m%d')}.ics"
|
|
193
|
-
|
|
194
|
-
send_data(
|
|
195
|
-
content,
|
|
196
|
-
filename: filename,
|
|
197
|
-
type: 'text/calendar; charset=UTF-8',
|
|
198
|
-
disposition: 'attachment'
|
|
199
|
-
)
|
|
200
|
-
end
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Important Notes
|
|
204
|
-
|
|
205
|
-
1. Time Handling:
|
|
206
|
-
- Always provide times in UTC to the Event constructor
|
|
207
|
-
- Use the timezone parameter to specify the display timezone
|
|
208
|
-
- All-day events should use beginning_of_day.utc and end_of_day.utc
|
|
148
|
+
Save `event.uid`. To reschedule or cancel later, rebuild the event with the same `uid` and a higher `sequence`, then send `method: :request` again (for a reschedule) or `method: :cancel`. See [Updating and cancelling invites](CONFIGURATION.md#updating-and-cancelling-invites). The [example Rails app](Example/calendar_app) has a working implementation.
|
|
209
149
|
|
|
210
|
-
|
|
211
|
-
- Physical location goes in the `location` parameter
|
|
212
|
-
- Virtual meeting URL goes in the `url` parameter
|
|
213
|
-
- They are handled separately for better calendar integration
|
|
150
|
+
## Things to know
|
|
214
151
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
152
|
+
- Pass `start_time` and `end_time` as UTC `Time`s. `timezone` only controls how the time is displayed. It must be `"UTC"` or an IANA name such as `"America/New_York"`, and defaults to `CalInvite.configuration.timezone` (`"UTC"`).
|
|
153
|
+
- Put the physical place in `location` and the video-call link in `url`. Each provider formats them its own way.
|
|
154
|
+
- Invalid input raises `ArgumentError`: a misspelled attribute (`locaton:`), an unknown timezone, an end time before the start time, or an unsupported provider.
|
|
155
|
+
- CalInvite generates links and invites but doesn't receive RSVP replies. It also can't set options that exist only in the Google and Microsoft APIs, such as guest permissions. See [Tracking RSVPs](CONFIGURATION.md#tracking-rsvps-what-the-gem-does-and-doesnt-do).
|
|
156
|
+
- Output is cheap to build, so nothing is cached and it's generated fresh on every call. To cache it in your app, or answer repeat downloads with `304 Not Modified`, see [CACHING.md](CACHING.md).
|
|
219
157
|
|
|
220
|
-
|
|
158
|
+
Global settings:
|
|
221
159
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
160
|
+
```ruby
|
|
161
|
+
# config/initializers/cal_invite.rb
|
|
162
|
+
CalInvite.configure do |config|
|
|
163
|
+
config.timezone = "America/New_York" # default for events that don't set one
|
|
164
|
+
end
|
|
165
|
+
```
|
|
227
166
|
|
|
228
|
-
|
|
167
|
+
## Upgrading to 0.3.0
|
|
229
168
|
|
|
230
|
-
|
|
169
|
+
0.3.0 is stricter. Unknown attributes, invalid timezones and invalid time ranges now raise `ArgumentError`, where 0.2.x silently produced a broken invite. The gem also no longer loads all of `active_support/core_ext`. In plain Ruby, `1.hour` and `Time.current` no longer work unless you require them yourself.
|
|
231
170
|
|
|
232
|
-
|
|
171
|
+
The `cache_store`/`cache_prefix`/`cache_expires_in` settings and the top-level `::BaseProvider` constant are deprecated. They still work, but print a warning, and they'll be removed in 0.4.0. The full list of changes is in [CHANGELOG.md](CHANGELOG.md).
|
|
233
172
|
|
|
234
|
-
|
|
173
|
+
## Documentation
|
|
235
174
|
|
|
236
|
-
|
|
175
|
+
- [CONFIGURATION.md](CONFIGURATION.md): every attribute, provider and option, plus guides for email invites, updates and cancellations, RSVP status, COUNTER proposals, and timezones.
|
|
176
|
+
- [API reference](https://dashapi-ai.github.io/cal-invite/): generated from the YARD comments (`bundle exec rake rdoc` builds it locally).
|
|
177
|
+
- [CACHING.md](CACHING.md): caching output in plain Ruby, Rails, Sinatra, Hanami and Grape, including HTTP caching for `.ics` feeds.
|
|
178
|
+
- [CHANGELOG.md](CHANGELOG.md): release history.
|
|
237
179
|
|
|
238
|
-
|
|
180
|
+
## Development
|
|
239
181
|
|
|
240
182
|
```bash
|
|
241
|
-
|
|
242
|
-
bundle exec rake test
|
|
183
|
+
bin/setup # install dependencies
|
|
184
|
+
bundle exec rake test # full suite (+ coverage report in coverage/)
|
|
185
|
+
bundle exec rake test TEST=test/cal_invite/event_test.rb # one file
|
|
186
|
+
bin/console # IRB with the local lib/ loaded
|
|
243
187
|
```
|
|
244
188
|
|
|
245
|
-
|
|
189
|
+
To try unreleased changes in the [example Rails app](Example/calendar_app), switch its Gemfile to the commented-out `gem "cal-invite", path: "../../"` line and run `bundle install`. Switch it back before committing.
|
|
246
190
|
|
|
247
|
-
|
|
248
|
-
event = CalInvite::Event.new(
|
|
249
|
-
title: "Demo",
|
|
250
|
-
start_time: Time.utc(2026, 11, 1, 15),
|
|
251
|
-
end_time: Time.utc(2026, 11, 1, 16),
|
|
252
|
-
timezone: "America/New_York",
|
|
253
|
-
organizer: { email: "me@example.com", name: "Me" },
|
|
254
|
-
attendees: ["guest@example.com"],
|
|
255
|
-
show_attendees: true
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
puts event.generate_calendar_url(:ics, method: :request) # raw .ics content
|
|
259
|
-
event.generate_calendar_url(:google) # provider URL
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
**3. Example Rails app** (`Example/calendar_app`) — for end-to-end checks in a real browser or mail client (downloading the `.ics`, RSVP buttons, etc.). Its Gemfile points at the published gem; to test unreleased changes, temporarily swap `gem "cal-invite"` for the commented-out `gem "cal-invite", path: "../../"` line and run `bundle install`. Revert the Gemfile and re-run `bundle install` before committing.
|
|
191
|
+
To release, bump `lib/cal_invite/version.rb`, update `CHANGELOG.md`, merge to `main`, and push a `vX.Y.Z` tag. CI then runs the tests, publishes the gem to RubyGems and deploys the docs.
|
|
263
192
|
|
|
264
193
|
## Contributing
|
|
265
194
|
|
|
266
|
-
Bug reports and pull requests are welcome on GitHub
|
|
195
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/DashAPI-ai/cal-invite). Please add tests for your change and run `bundle exec rake test` before opening a PR. To report a security issue, see [SECURITY.md](SECURITY.md). Everyone taking part in the project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
|
267
196
|
|
|
268
|
-
##
|
|
197
|
+
## Sponsor
|
|
269
198
|
|
|
270
|
-
|
|
199
|
+
CalInvite is sponsored by [DashAPI](https://dashapi.ai).
|
|
271
200
|
|
|
272
|
-
|
|
201
|
+
<a href="https://dashapi.ai">
|
|
202
|
+
<picture>
|
|
203
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/sponsors/dashapi-logo-dark.svg">
|
|
204
|
+
<img src="assets/sponsors/dashapi-logo-light.svg" alt="DashAPI" width="140">
|
|
205
|
+
</picture>
|
|
206
|
+
</a>
|
|
273
207
|
|
|
274
208
|
## License
|
|
275
209
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
## Code of Conduct
|
|
279
|
-
|
|
280
|
-
Everyone interacting in the Cal::Invite project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/DashAPI-ai/cal-invite/blob/master/CODE_OF_CONDUCT.md).
|
|
210
|
+
Released under the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -23,9 +23,11 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
|
23
23
|
rdoc.options << '--markup' << 'markdown'
|
|
24
24
|
rdoc.options << '--all'
|
|
25
25
|
rdoc.options << '--exclude' << '^(test|spec|features)/'
|
|
26
|
+
rdoc.options << '--template-stylesheets' << 'assets/rdoc-overrides.css'
|
|
26
27
|
|
|
27
28
|
# Include files to document
|
|
28
|
-
rdoc.rdoc_files.include('README.md', '
|
|
29
|
+
rdoc.rdoc_files.include('README.md', 'CONFIGURATION.md', 'CACHING.md', 'CHANGELOG.md',
|
|
30
|
+
'SECURITY.md', 'CODE_OF_CONDUCT.md', 'LICENSE.txt', 'lib/**/*.rb')
|
|
29
31
|
rdoc.rdoc_files.exclude('lib/cal_invite/version.rb')
|
|
30
32
|
end
|
|
31
33
|
|
data/lib/cal_invite/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cal-invite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephane Paquet
|
|
@@ -105,6 +105,7 @@ files:
|
|
|
105
105
|
- ".rdoc_options"
|
|
106
106
|
- ".rubocop.yml"
|
|
107
107
|
- AGENTS.md
|
|
108
|
+
- CACHING.md
|
|
108
109
|
- CHANGELOG.md
|
|
109
110
|
- CODE_OF_CONDUCT.md
|
|
110
111
|
- CONFIGURATION.md
|
|
@@ -112,7 +113,6 @@ files:
|
|
|
112
113
|
- README.md
|
|
113
114
|
- Rakefile
|
|
114
115
|
- SECURITY.md
|
|
115
|
-
- assets/social/cal-invite-github.png
|
|
116
116
|
- gemfiles/Gemfile.rails6
|
|
117
117
|
- gemfiles/Gemfile.rails7
|
|
118
118
|
- gemfiles/Gemfile.rails8
|
|
@@ -130,7 +130,6 @@ files:
|
|
|
130
130
|
- lib/cal_invite/providers/outlook.rb
|
|
131
131
|
- lib/cal_invite/providers/yahoo.rb
|
|
132
132
|
- lib/cal_invite/version.rb
|
|
133
|
-
- lib/tasks/rdoc.rake
|
|
134
133
|
- sig/cal/invite.rbs
|
|
135
134
|
homepage: https://github.com/DashAPI-ai/cal-invite
|
|
136
135
|
licenses:
|
|
@@ -138,7 +137,8 @@ licenses:
|
|
|
138
137
|
metadata:
|
|
139
138
|
homepage_uri: https://github.com/DashAPI-ai/cal-invite
|
|
140
139
|
source_code_uri: https://github.com/DashAPI-ai/cal-invite
|
|
141
|
-
changelog_uri: https://github.com/DashAPI-ai/cal-invite/blob/
|
|
140
|
+
changelog_uri: https://github.com/DashAPI-ai/cal-invite/blob/main/CHANGELOG.md
|
|
141
|
+
documentation_uri: https://dashapi-ai.github.io/cal-invite/
|
|
142
142
|
post_install_message:
|
|
143
143
|
rdoc_options:
|
|
144
144
|
- "--title"
|
|
Binary file
|
data/lib/tasks/rdoc.rake
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# lib/tasks/rdoc.rake
|
|
2
|
-
require 'rdoc/task'
|
|
3
|
-
|
|
4
|
-
RDoc::Task.new do |rdoc|
|
|
5
|
-
rdoc.rdoc_dir = 'doc'
|
|
6
|
-
rdoc.title = 'CalInvite Documentation'
|
|
7
|
-
rdoc.main = 'README.md'
|
|
8
|
-
rdoc.options << '--line-numbers'
|
|
9
|
-
rdoc.options << '--charset' << 'UTF-8'
|
|
10
|
-
rdoc.options << '--markup' << 'markdown'
|
|
11
|
-
rdoc.rdoc_files.include('README.md', 'LICENSE', 'lib/**/*.rb')
|
|
12
|
-
rdoc.rdoc_files.exclude('lib/cal_invite/version.rb')
|
|
13
|
-
end
|