calendav 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc244efe85f65a2f9c4da19099a2a2e564a0b4fda5a1fafdcf6f37dd436c1f12
4
- data.tar.gz: 101adff024b6f5d6b9d6fca3ea96c273facacbd0f66989175c0be8171633c228
3
+ metadata.gz: 5f622fc58cda7b2c94bf7c6eff0056db4532e281bfd1e556a43f16ba8edac222
4
+ data.tar.gz: 075b5acb58221171aec66d8e9d0234410fa3f30cd0c790776ca0f023187f0d27
5
5
  SHA512:
6
- metadata.gz: 1ee524964c2799df6fdf47dce9d007c6332ebe8e6fe8d2f9265f57bfccba4117d7a508520267fa5e025dcb8c209efeb1bd01f1c73f31edf1ee4cded5d8259def
7
- data.tar.gz: 2df0276f3030c0fd1ea0cc37a32833326e7e2a0072aa4c6260c92e0e35ef635816e1b047d65b9db63e8b9c937ce4edc2c86ecb45d34ad8410c49229b5fcd06ed
6
+ metadata.gz: 596ecccf27b7cd0aeb697b4863f3c10c79e9ec8aaf2ca66786e2f31a94e5cf865a5835a8942a4c0189f7bfe040f5cde540ee7f10a09e0ec3cb693ae152923d09
7
+ data.tar.gz: 0ff81da9e901dfe16081a31a4df72319a6462ef939aa4aa83ebcb74792684e9b26b9999430159300192816e9ce9fdd31edbf04f57c6b0b28aa0cd85e9d9fe726
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.5.0 - 2025-08-02
4
+
5
+ * Support for todos (Josh Huckabee #8, #9)
6
+ * Improved documentation for development/testing environments (Josh Huckabee #7)
7
+ * Support for expanded recurring events (Sayooj Surendran #12)
8
+ * Support for OAuth authentication header (Ilya Nikitenkov #13)
9
+ * Updated tested Ruby versions to 3.1-3.4 (2.7 and 3.0 are no longer officially supported).
10
+
3
11
  ## 0.4.0 - 2023-02-27
4
12
 
5
13
  * **Breaking**: calls to create/update events return event objects that only have a URL and etag populated (so: no calendar data), or nil if the request failed due to an etag precondition. This is instead of returning true on success or false on failure.
data/README.md CHANGED
@@ -23,7 +23,7 @@ credentials = Calendav::Credentials::Standard.new(
23
23
  host: "https://www.example.com/caldav",
24
24
  username: "example",
25
25
  password: "secret",
26
- authentication: :basic_auth # or :bearer_token
26
+ authentication: :basic_auth # :bearer_token and :oauth also supported
27
27
  )
28
28
  ```
29
29
 
@@ -100,6 +100,16 @@ end
100
100
 
101
101
  The returned events have a unique URL (just like calendars), an `etag` (which changes when the event changes), and `calendar_data`, which is stored in the [iCalendar](https://datatracker.ietf.org/doc/html/rfc5545) format. The event objects returned currently parse the summary out of the calendar data, but nothing else - further attributes may be made visible, but for full control it's recommended you use the [icalendar](https://github.com/icalendar/icalendar) gem.
102
102
 
103
+ To get recurring events, you can use the optional `expand_recurring_events` argument.
104
+
105
+ ```ruby
106
+ events = client.events.list(
107
+ calendar_url, from: Time.new(2021, 1, 1), to: Time.new(2022, 1, 1),
108
+ expand_recurring_events: true
109
+ )
110
+ ```
111
+ The recurrent events will be present in the `calendar_data`.
112
+
103
113
  If you have an event's URL, you can fetch the details of just that event directly:
104
114
 
105
115
  ```ruby
@@ -151,6 +161,55 @@ client.events.delete(event_url)
151
161
  client.events.delete(event_url, etag: event.etag)
152
162
  ```
153
163
 
164
+ ### Todos
165
+
166
+ You can also use a calendar's URL to retrieve its todos.
167
+
168
+ ```ruby
169
+ todos = client.todos.list(calendar_url)
170
+ todos.each do |todo|
171
+ puts todo.url
172
+ puts todo.summary
173
+ puts todo.status
174
+ puts todo.due
175
+ end
176
+ ```
177
+
178
+ Just like events, returned todos have a unique URL, an `etag` (which changes when the todo changes), and `calendar_data`. The todo objects returned currently parse the summary, status, and due date out of the calendar data.
179
+
180
+ If you have a todo's URL, you can fetch the details of just that todo directly:
181
+
182
+ ```ruby
183
+ todo = client.todos.find(todo_url)
184
+ ```
185
+
186
+ Creating todos, just like creating events, requires a unique identifier. You will also need to generate the iCalendar data - again, the [icalendar](https://github.com/icalendar/icalendar) gem is very helpful for this.
187
+
188
+ ```ruby
189
+ require "securerandom"
190
+ require "icalendar"
191
+
192
+ ics = Icalendar::Calendar.new
193
+ ics.todo do |todo|
194
+ # ...
195
+ end
196
+ ics.publish
197
+
198
+ identifier = "#{SecureRandom.uuid}.ics"
199
+ # The returned todo has just the URL and the etag, no calendar data:
200
+ todo_scaffold = client.todos.create(calendar.url, identifier, ics.to_ical)
201
+ ```
202
+
203
+ Updating and deleting todos is similar to updating events:
204
+
205
+ ```ruby
206
+ # Update the todo
207
+ client.todos.update(todo_url, ics.to_ical)
208
+
209
+ # Delete the todo
210
+ client.todos.delete(todo_url)
211
+ ```
212
+
154
213
  ### Synchronising
155
214
 
156
215
  If you are maintaining your own copy/history of events from a CalDAV server, then it's highly recommended you take advantage of the WebDAV-Sync protocol (should the calendar provider support it). Using Calendav, the suggested approach is:
@@ -198,7 +257,7 @@ While a lot of the core CalDAV functionality is covered and this gem is useful a
198
257
  * Further iCalendar parsing for Event objects.
199
258
  * Automated tests against FastMail and possibly other providers (Apple and Google are already covered).
200
259
  * Locking/unlocking of events as per the WebCAL RFC.
201
- * Support for VTODO, VJOURNAL, VFREEBUSY and any other components beyond VEVENT.
260
+ * Support for VJOURNAL, VFREEBUSY and any other components beyond VEVENT.
202
261
 
203
262
  ## Installation
204
263
 
@@ -232,11 +291,21 @@ The work done in previous Ruby CalDAV clients [RubyCaldav](https://github.com/di
232
291
 
233
292
  ## Development
234
293
 
235
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
294
+ After checking out the repo, run `bin/setup` to install dependencies and configure the required credentials. This will copy the `.env.example` file to a `.env` file. Make sure to configure the `.env` file with your own Apple and Google credentials.
295
+
296
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
236
297
 
237
298
  ## Tests
238
299
 
239
- The test suite currently only runs against Google and Apple accounts I've created especially for this purpose - and the credentials are not public. I realise this makes contributions more difficult, and I'm open to finding better ways to handle this. I did look into running a CalDAV server within the test suite, but couldn't find anything small and easy enough for that purpose. But also: testing against common CalDAV servers does help to ensure this gem is truly useful (and has knowledge of their idiosyncrasies).
300
+ The test suite depends on a locally running instance of a CalDAV server to work properly. We prefer [Radicale](https://radicale.org/v3.html) for this. You can run it locally with Docker and the provided configuration in `spec/data/radicale` with the following command:
301
+
302
+ docker run -v $PWD/spec/data/radicale:/var/radicale \
303
+ --name radicale \
304
+ --publish 8000:8000 \
305
+ --detach \
306
+ xlrl/radicale
307
+
308
+ With the CalDAV server running, you can now run `rake spec` to run the tests.
240
309
 
241
310
  ## Contributing
242
311
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./contextual_url"
4
- require_relative "./parsers/calendar_xml"
3
+ require_relative "contextual_url"
4
+ require_relative "parsers/calendar_xml"
5
5
 
6
6
  module Calendav
7
7
  class Calendar
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./contextual_url"
4
- require_relative "./endpoint"
5
- require_relative "./clients/calendars_client"
6
- require_relative "./clients/events_client"
7
- require_relative "./requests/current_user_principal"
3
+ require_relative "contextual_url"
4
+ require_relative "endpoint"
5
+ require_relative "clients/calendars_client"
6
+ require_relative "clients/events_client"
7
+ require_relative "clients/todos_client"
8
+ require_relative "requests/current_user_principal"
8
9
 
9
10
  module Calendav
10
11
  class Client
@@ -21,6 +22,10 @@ module Calendav
21
22
  @events = Clients::EventsClient.new(self, endpoint, credentials)
22
23
  end
23
24
 
25
+ def todos
26
+ @todos = Clients::TodosClient.new(self, endpoint, credentials)
27
+ end
28
+
24
29
  def principal_url
25
30
  @principal_url ||= begin
26
31
  request = Requests::CurrentUserPrincipal.call
@@ -82,7 +82,7 @@ module Calendav
82
82
  )
83
83
  end
84
84
 
85
- def update(url, attributes)
85
+ def update(url, attributes) # rubocop:disable Naming/PredicateMethod
86
86
  request = Requests::UpdateCalendar.call(attributes)
87
87
  endpoint
88
88
  .proppatch(request.to_xml, url: url)
@@ -39,8 +39,10 @@ module Calendav
39
39
  )
40
40
  end
41
41
 
42
- def list(calendar_url, from: nil, to: nil)
43
- request = Requests::ListEvents.call(from: from, to: to)
42
+ def list(calendar_url, from: nil, to: nil, expand_recurring_events: false)
43
+ request = Requests::ListEvents.call(
44
+ from: from, to: to, expand_recurring_events: expand_recurring_events
45
+ )
44
46
 
45
47
  endpoint
46
48
  .report(request.to_xml, url: calendar_url, depth: 1)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+ require_relative "../todo"
5
+ require_relative "../requests/list_todos"
6
+
7
+ module Calendav
8
+ module Clients
9
+ class TodosClient
10
+ def initialize(client, endpoint, credentials)
11
+ @client = client
12
+ @endpoint = endpoint
13
+ @credentials = credentials
14
+ end
15
+
16
+ def create(calendar_url, todo_identifier, ics)
17
+ todo_url = merged_url(calendar_url, todo_identifier)
18
+ result = endpoint.put(ics, url: todo_url, content_type: :ics)
19
+
20
+ Todo.new(
21
+ url: result.headers["Location"] || todo_url,
22
+ etag: result.headers["ETag"]
23
+ )
24
+ end
25
+
26
+ def delete(todo_url, etag: nil)
27
+ endpoint.delete(url: todo_url, etag: etag)
28
+ rescue PreconditionError
29
+ false
30
+ end
31
+
32
+ def find(todo_url)
33
+ response = endpoint.get(url: todo_url)
34
+
35
+ Todo.new(
36
+ url: todo_url,
37
+ calendar_data: response.body.to_s,
38
+ etag: response.headers["ETag"]
39
+ )
40
+ end
41
+
42
+ def list(calendar_url)
43
+ request = Requests::ListTodos.call
44
+
45
+ endpoint
46
+ .report(request.to_xml, url: calendar_url, depth: 1)
47
+ .reject { |node| node.xpath(".//caldav:calendar-data").text.empty? }
48
+ .collect { |node| Todo.from_xml(calendar_url, node) }
49
+ end
50
+
51
+ def update(todo_url, ics, etag: nil)
52
+ result = endpoint.put(
53
+ ics, url: todo_url, content_type: :ics, etag: etag
54
+ )
55
+
56
+ Todo.new(
57
+ url: todo_url,
58
+ etag: result.headers["ETag"]
59
+ )
60
+ rescue PreconditionError
61
+ nil
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :client, :endpoint, :credentials
67
+
68
+ def merged_url(calendar_url, todo_identifier)
69
+ "#{calendar_url.delete_suffix('/')}/#{todo_identifier}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -3,7 +3,7 @@
3
3
  require "http"
4
4
  require "uri"
5
5
 
6
- require_relative "./standard"
6
+ require_relative "standard"
7
7
 
8
8
  module Calendav
9
9
  module Credentials
@@ -3,7 +3,7 @@
3
3
  require "http"
4
4
  require "uri"
5
5
 
6
- require_relative "./standard"
6
+ require_relative "standard"
7
7
 
8
8
  module Calendav
9
9
  module Credentials
@@ -3,7 +3,7 @@
3
3
  require "http"
4
4
  require "uri"
5
5
 
6
- require_relative "./standard"
6
+ require_relative "standard"
7
7
 
8
8
  module Calendav
9
9
  module Credentials
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./contextual_url"
4
- require_relative "./error_handler"
5
- require_relative "./parsers/response_xml"
3
+ require_relative "contextual_url"
4
+ require_relative "error_handler"
5
+ require_relative "parsers/response_xml"
6
6
 
7
7
  module Calendav
8
8
  class Endpoint
@@ -16,7 +16,7 @@ module Calendav
16
16
  @timeout = timeout
17
17
  end
18
18
 
19
- def delete(url:, etag: nil)
19
+ def delete(url:, etag: nil) # rubocop:disable Naming/PredicateMethod
20
20
  request(:delete, url: url, http: with_headers(etag: etag))
21
21
  .status
22
22
  .success?
@@ -79,12 +79,14 @@ module Calendav
79
79
 
80
80
  attr_reader :credentials, :timeout
81
81
 
82
- def authenticated
82
+ def authenticated # rubocop:disable Metrics/MethodLength
83
83
  case credentials.authentication
84
84
  when :basic_auth
85
85
  HTTP.basic_auth(user: credentials.username, pass: credentials.password)
86
86
  when :bearer_token
87
87
  HTTP.auth("Bearer #{credentials.password}")
88
+ when :oauth
89
+ HTTP.auth("OAuth #{credentials.password}")
88
90
  else
89
91
  raise "Unexpected authentication approach: " \
90
92
  "#{credentials.authentication}"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./errors"
3
+ require_relative "errors"
4
4
 
5
5
  module Calendav
6
6
  class ErrorHandler
@@ -7,7 +7,7 @@ module Calendav
7
7
  attr_reader :xml, :original
8
8
 
9
9
  def initialize(xml, original)
10
- super original.message
10
+ super(original.message)
11
11
 
12
12
  @xml = xml
13
13
  @original = original
@@ -18,7 +18,7 @@ module Calendav
18
18
  attr_reader :response
19
19
 
20
20
  def initialize(response)
21
- super response.status.to_s
21
+ super(response.status.to_s)
22
22
 
23
23
  @response = response
24
24
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./contextual_url"
4
- require_relative "./parsers/event_xml"
3
+ require_relative "contextual_url"
4
+ require_relative "parsers/event_xml"
5
5
 
6
6
  module Calendav
7
7
  class Event
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calendav
4
+ module Parsers
5
+ class TodoXML
6
+ def self.call(...)
7
+ new(...).call
8
+ end
9
+
10
+ def initialize(element)
11
+ @element = element
12
+ end
13
+
14
+ def call
15
+ {
16
+ calendar_data: value(".//caldav:calendar-data"),
17
+ etag: value(".//dav:getetag")
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :element
24
+
25
+ def value(xpath)
26
+ node = element.xpath(xpath)
27
+ return nil if node.children.empty?
28
+
29
+ if node.children.any?(&:element?)
30
+ node.children.select(&:element?).collect(&:to_xml).join
31
+ else
32
+ node.children.text
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -11,9 +11,10 @@ module Calendav
11
11
  new(...).call
12
12
  end
13
13
 
14
- def initialize(from:, to:)
14
+ def initialize(from:, to:, expand_recurring_events:)
15
15
  @from = from
16
16
  @to = to
17
+ @expand_recurring_events = expand_recurring_events
17
18
  end
18
19
 
19
20
  def call
@@ -21,7 +22,13 @@ module Calendav
21
22
  xml["caldav"].public_send("calendar-query", NAMESPACES) do
22
23
  xml["dav"].prop do
23
24
  xml["dav"].getetag
24
- xml["caldav"].public_send(:"calendar-data")
25
+ if expand_recurring_events? && range?
26
+ xml["caldav"].public_send(:"calendar-data") do
27
+ xml["caldav"].expand(start: from, end: to)
28
+ end
29
+ else
30
+ xml["caldav"].public_send(:"calendar-data")
31
+ end
25
32
  end
26
33
  xml["caldav"].filter do
27
34
  xml["caldav"].public_send(:"comp-filter", name: "VCALENDAR") do
@@ -40,6 +47,10 @@ module Calendav
40
47
 
41
48
  private
42
49
 
50
+ def expand_recurring_events?
51
+ @expand_recurring_events
52
+ end
53
+
43
54
  def from
44
55
  return nil if @from.nil?
45
56
 
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ require_relative "../namespaces"
6
+
7
+ module Calendav
8
+ module Requests
9
+ class ListTodos
10
+ def self.call(...)
11
+ new.call
12
+ end
13
+
14
+ def call
15
+ Nokogiri::XML::Builder.new do |xml|
16
+ xml["caldav"].public_send("calendar-query", NAMESPACES) do
17
+ xml["dav"].prop do
18
+ xml["dav"].getetag
19
+ xml["caldav"].public_send(:"calendar-data")
20
+ end
21
+ xml["caldav"].filter do
22
+ xml["caldav"].public_send(:"comp-filter", name: "VCALENDAR") do
23
+ xml["caldav"].public_send(:"comp-filter", name: "VTODO")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def from
33
+ return nil if @from.nil?
34
+
35
+ @from.utc.iso8601.delete(":-")
36
+ end
37
+
38
+ def to
39
+ return nil if @to.nil?
40
+
41
+ @to.utc.iso8601.delete(":-")
42
+ end
43
+
44
+ def range?
45
+ to || from
46
+ end
47
+ end
48
+ end
49
+ end
@@ -38,6 +38,7 @@ module Calendav
38
38
  :"supported-calendar-component-set"
39
39
  ) do
40
40
  xml["caldav"].comp name: "VEVENT"
41
+ xml["caldav"].comp name: "VTODO"
41
42
  end
42
43
  end
43
44
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "contextual_url"
4
+ require_relative "parsers/todo_xml"
5
+
6
+ module Calendav
7
+ class Todo
8
+ ATTRIBUTES = %i[url calendar_data etag].freeze
9
+
10
+ def self.from_xml(host, node)
11
+ new(
12
+ {
13
+ url: ContextualURL.call(host, node.xpath("./dav:href").text)
14
+ }.merge(
15
+ Parsers::TodoXML.call(node)
16
+ )
17
+ )
18
+ end
19
+
20
+ def initialize(attributes = {})
21
+ @attributes = attributes
22
+ end
23
+
24
+ ATTRIBUTES.each do |attribute|
25
+ define_method(attribute) { attributes[attribute] }
26
+ end
27
+
28
+ def to_h
29
+ attributes.dup
30
+ end
31
+
32
+ def summary
33
+ inner_todo.summary
34
+ end
35
+
36
+ def due
37
+ inner_todo.due
38
+ end
39
+
40
+ def status
41
+ inner_todo.status
42
+ end
43
+
44
+ def unloaded?
45
+ calendar_data.nil?
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :attributes
51
+
52
+ def inner_calendar
53
+ Icalendar::Calendar.parse(calendar_data).first
54
+ end
55
+
56
+ def inner_todo
57
+ @inner_todo = inner_calendar.todos.first
58
+ end
59
+ end
60
+ end
data/lib/calendav.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./calendav/credentials/apple"
4
- require_relative "./calendav/credentials/fastmail"
5
- require_relative "./calendav/credentials/google"
6
- require_relative "./calendav/client"
3
+ require_relative "calendav/credentials/apple"
4
+ require_relative "calendav/credentials/fastmail"
5
+ require_relative "calendav/credentials/google"
6
+ require_relative "calendav/client"
7
7
 
8
8
  module Calendav
9
9
  PROVIDERS = {
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calendav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-02-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: http
@@ -52,161 +51,6 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: dotenv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: google-api-client
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: googleauth
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: rake
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rspec
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: timecop
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: tzinfo
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '0'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - ">="
165
- - !ruby/object:Gem::Version
166
- version: '0'
167
- - !ruby/object:Gem::Dependency
168
- name: vcr
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - ">="
172
- - !ruby/object:Gem::Version
173
- version: '0'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - ">="
179
- - !ruby/object:Gem::Version
180
- version: '0'
181
- - !ruby/object:Gem::Dependency
182
- name: vcr_assistant
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - ">="
186
- - !ruby/object:Gem::Version
187
- version: '0'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - ">="
193
- - !ruby/object:Gem::Version
194
- version: '0'
195
- - !ruby/object:Gem::Dependency
196
- name: webmock
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - ">="
200
- - !ruby/object:Gem::Version
201
- version: '0'
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - ">="
207
- - !ruby/object:Gem::Version
208
- version: '0'
209
- description:
210
54
  email:
211
55
  - pat@freelancing-gods.com
212
56
  executables: []
@@ -221,6 +65,7 @@ files:
221
65
  - lib/calendav/client.rb
222
66
  - lib/calendav/clients/calendars_client.rb
223
67
  - lib/calendav/clients/events_client.rb
68
+ - lib/calendav/clients/todos_client.rb
224
69
  - lib/calendav/contextual_url.rb
225
70
  - lib/calendav/credentials/apple.rb
226
71
  - lib/calendav/credentials/fastmail.rb
@@ -236,14 +81,17 @@ files:
236
81
  - lib/calendav/parsers/event_xml.rb
237
82
  - lib/calendav/parsers/response_xml.rb
238
83
  - lib/calendav/parsers/sync_xml.rb
84
+ - lib/calendav/parsers/todo_xml.rb
239
85
  - lib/calendav/requests/calendar_home_set.rb
240
86
  - lib/calendav/requests/current_user_principal.rb
241
87
  - lib/calendav/requests/list_calendars.rb
242
88
  - lib/calendav/requests/list_events.rb
89
+ - lib/calendav/requests/list_todos.rb
243
90
  - lib/calendav/requests/make_calendar.rb
244
91
  - lib/calendav/requests/sync_collection.rb
245
92
  - lib/calendav/requests/update_calendar.rb
246
93
  - lib/calendav/sync_collection.rb
94
+ - lib/calendav/todo.rb
247
95
  homepage: https://github.com/pat/calendav
248
96
  licenses:
249
97
  - Hippocratic-2.1
@@ -252,7 +100,6 @@ metadata:
252
100
  source_code_uri: https://github.com/pat/calendav
253
101
  changelog_uri: https://github.com/pat/calendav/blob/main/CHANGELOG.md
254
102
  rubygems_mfa_required: 'true'
255
- post_install_message:
256
103
  rdoc_options: []
257
104
  require_paths:
258
105
  - lib
@@ -267,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
114
  - !ruby/object:Gem::Version
268
115
  version: '0'
269
116
  requirements: []
270
- rubygems_version: 3.2.32
271
- signing_key:
117
+ rubygems_version: 3.6.9
272
118
  specification_version: 4
273
119
  summary: CalDAV client
274
120
  test_files: []