icalPal 3.8.3 → 3.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53e9d1413da61496024e336dfe5164e680f211603b5cd647fa9525a0aaff5899
4
- data.tar.gz: a107b8fcf0dbee3eafc77c80d45b450ab2fad5d1ec2676299b3dc57f0070d5f4
3
+ metadata.gz: 726009d217b28c21e4a35339eef172f033429792b68d4c36157e6234f6221cab
4
+ data.tar.gz: cf2ee63838a89a8acf25719154d03654e1bbf1fe47459481ddcb5e09f19a31f0
5
5
  SHA512:
6
- metadata.gz: 64bb5a9d98f5fd8c8aca7d184401e39ab1b6621445a3481da511ea7506ae82c7506f1e1e88e4344ca0954b79a973e79867dbec97f312fc35f3129c91fd5cbdba
7
- data.tar.gz: 9796c5d7913b7ff5b0d642e61318e84c787c866b0a8c2bbc91344e566a81e2e187d57e22a70920e940a22bfdd4eb4d01d500f9ae7094fc92c91a80e8e671fd03
6
+ metadata.gz: 7120724aaa3be333484a6feca64cb1cf5b6f5ded5e93b5a4f6c99ec6e247ddd659bcc7c4f3af70b7b3e81dc28b1221bebac88d29c70bc497a89f242689ae088b
7
+ data.tar.gz: b452c105b9ea954aa9c68a9b7a4088599b32b0b1b29e3f783804ec692eef65e8c7a1d916d3b9061c475ed880b950f9eebf31d870c8eda7751f33e4fd79bc9ae9
data/README.md CHANGED
@@ -51,7 +51,7 @@ differences to be aware of.
51
51
  * *eventsFrom* is not supported. Instead there is *--from*, *--to*, and *--days*
52
52
  * *uncompletedTasks* is simply *tasks*
53
53
  * *undatedUncompletedTasks* is simply *undatedTasks*
54
- * *tasksDueBefore:DATE* is not yet supported
54
+ * *tasksDueBefore:DATE* uses *--from*, *--to*, and *--days* instead of *:DATE*
55
55
  * The command can go anywhere; it doesn't have to be the last argument
56
56
  * Property separators are comma-delimited
57
57
 
@@ -68,16 +68,23 @@ Shows only reminders that have a due date.
68
68
 
69
69
  ```icalPal reminders```
70
70
 
71
- *reminders*, *datedReminders*, and *undatedReminders* can be used instead of *tasks*
71
+ *reminders*, *datedReminders*, *undatedReminders*, and
72
+ *remindersDueBefore* can be used instead of *tasks*
73
+
74
+ Reminders can also be viewed in the *Scheduled Reminders* calendar,
75
+ using the *tasks* commands. Repeating reminders are treated the same
76
+ as repeating events.
72
77
 
73
78
  ### Additional options
74
79
 
75
80
  * Options can be abbreviated, so long as they are unique. Eg., ```icalPal -c ev --da 3``` is the same as ```icalPal -c events --days 3```.
76
81
  * The ```-c``` part is optional, but you cannot abbreviate the command if you leave it off.
77
- * Use ```-o``` to print the output in different formats. CSV or JSON are intertesting choices.
82
+ * Use ```-o``` to print the output in different formats. CSV or JSON are interesting choices.
78
83
  * Copy your Calendar or Reminders database file and use ```--db``` on it.
79
84
  * ```--it``` and ```--et``` will filter by Calendar *type*. Types are **Local**, **Exchange**, **CalDAV**, **MobileMe**, **Subscribed**, **Birthdays**, and **Reminders**
80
85
  * ```--il``` and ```-el``` will filter by Reminder list
86
+ * ```--id``` includes completed reminders
87
+ * ```--ed``` excludes uncompleted reminders
81
88
  * ```--ia``` includes *only* all-day events (opposite of ```--ea```)
82
89
  * ```--aep``` is like ```--iep```, but *adds* to the default property list instead of replacing it.
83
90
  * ```--sep``` to separate by any property, not just calendar (```--sc```) or date (```--sd```)
@@ -111,6 +118,8 @@ Several additional properties are available for each command.
111
118
 
112
119
  * Tasks
113
120
  * id
121
+ * grocery
122
+ * completed
114
123
  * group
115
124
  * section
116
125
  * tags
@@ -139,15 +148,19 @@ COMMAND must be one of the following:
139
148
  eventsRemaining Print events occurring between present time and midnight
140
149
  datedTasks Print tasks with a due date
141
150
  undatedTasks Print tasks with no due date
151
+ tasksDueBefore Print uncompleted tasks due between the given dates
152
+
153
+ stores can be used instead of accounts
154
+ reminders can be used instead of tasks
142
155
  ```
143
156
 
144
157
  Global options:
145
158
  ```
146
159
  -c, --cmd=COMMAND Command to run
147
160
  --db=DB Use DB file instead of Calendar
148
- (default: ["$HOME/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb", "/Users/user/Library/Calendars/Calendar.sqlitedb"]
161
+ (default: ["$HOME/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb", $HOME/Library/Calendars/Calendar.sqlitedb]
149
162
  For the tasks commands this should be a directory containing .sqlite files
150
- (default: $HOME/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores)
163
+ (default: "$HOME/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores")
151
164
  --cf=FILE Set config file path (default: $HOME/.icalpal)
152
165
  --norc Ignore ICALPAL and ICALPAL_CONFIG environment variables
153
166
  -o, --output=FORMAT Print as FORMAT (default: default)
@@ -169,6 +182,9 @@ Including/excluding accounts, calendars, reminders and items:
169
182
  --il=LISTS List of reminder lists to include
170
183
  --el=LISTS List of reminder lists to exclude
171
184
 
185
+ --id Include completed reminders
186
+ --ed Exclude uncompleted reminders
187
+
172
188
  --match=FIELD=REGEX Include only items whose FIELD matches REGEXP (ignoring case)
173
189
  ```
174
190
 
@@ -243,7 +259,7 @@ Formatting the output:
243
259
  Help:
244
260
  ```
245
261
  -h, --help Show this message
246
- -V, -v, --version Show version and exit (2.0.0)
262
+ -V, -v, --version Show version and exit (3.9.1)
247
263
  -d, --debug=LEVEL Set the logging level (default: warn)
248
264
  [debug, info, warn, error, fatal]
249
265
  ```
@@ -252,7 +268,7 @@ Environment variables:
252
268
  ```
253
269
  ICALPAL Additional arguments
254
270
  ICALPAL_CONFIG Additional arguments from a file
255
- (default: /Users/user/.icalpal)
271
+ (default: $HOME/.icalpal)
256
272
 
257
273
  Do not quote or escape values. Options set in ICALPAL override ICALPAL_CONFIG. Options on the command line override ICALPAL.
258
274
  ```
@@ -264,11 +280,12 @@ to mimic icalBuddy as much as possible.
264
280
 
265
281
  CSV, Hash, JSON, XML, and YAML print all fields for all items in their
266
282
  respective formats. From that you can analyze the results any way you
267
- like.
268
-
269
- [Remind](https://dianne.skoll.ca/projects/remind/) format uses a
283
+ like. [Remind](https://dianne.skoll.ca/projects/remind/) format uses a
270
284
  minimal implementation built into icalPal.
271
285
 
286
+ Control characters are escaped in these formats to ensure they remain
287
+ properly formatted.
288
+
272
289
  Other formats such as ANSI, HTML, Markdown, RDoc, and TOC, use Ruby's
273
290
  [RDoc::Markup](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup.html)
274
291
  framework to build and render the items.
@@ -276,8 +293,8 @@ framework to build and render the items.
276
293
  Each item to be printed is a new
277
294
  [RDoc::Markup::Document](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Document.html).
278
295
 
279
- When using one of the <em>separate by</em> options, a section header
280
- is added first. The section contains:
296
+ When using one of the _separate by_ options, a section header is added
297
+ first. The section contains:
281
298
 
282
299
  * [RDoc::Markup::BlankLine](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/BlankLine.html)
283
300
  (unless this is the first section)
@@ -315,9 +332,10 @@ It's only thanks to the efforts of [Jim
315
332
  Lawton](https://github.com/jimlawton) that it even compiles anymore.
316
333
 
317
334
  Instead of trying to understand and extend the existing code, I chose
318
- to start anew using my language of choice: Ruby. Using Ruby meant
319
- there is *much* less code; less than 2,000 lines vs. 7,000. It also means
320
- icalPal is multi-platform.
335
+ to start anew using my language of choice:
336
+ [Ruby](https://www.ruby-lang.org). Using Ruby meant there is *much*
337
+ less code; a little over 2,000 lines vs. 7,000. It also means icalPal
338
+ is multi-platform.
321
339
 
322
340
  I won't pretend to understand **why** you would want to run this on
323
341
  Linux or Windows. But since icalPal is written in Ruby and gets its
data/bin/icalPal CHANGED
@@ -191,7 +191,7 @@ $rows.each do |row|
191
191
  end
192
192
  end
193
193
 
194
- if ICalPal::Event === item
194
+ if item.is_a?(ICalPal::Event)
195
195
  # Check for all-day and cancelled events
196
196
  next if $opts[:ea] && item['all_day'].positive?
197
197
  next if $opts[:ia] && !item['all_day'].positive?
@@ -201,10 +201,18 @@ $rows.each do |row|
201
201
  item.recurring.each { |j| add(j) } :
202
202
  item.non_recurring.each { |j| add(j) }
203
203
  else
204
- # Check for dated reminders
205
- if ICalPal::Reminder === item
204
+ # Check for completed or dated reminders
205
+ if item.is_a?(ICalPal::Reminder)
206
+ next if $opts[:ed] && item['completed'].zero?
207
+ next unless $opts[:id] || item['completed'].zero?
208
+
206
209
  next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
207
210
  next if $opts[:dated] == 2 && (!item['due_date'] || item['due_date'].zero?)
211
+
212
+ if $opts[:dated] == 3
213
+ next unless item['due_date']
214
+ next unless item['due_date'].between?($opts[:from].to_i, $opts[:to].to_i)
215
+ end
208
216
  end
209
217
 
210
218
  add(item)
@@ -230,7 +238,7 @@ begin
230
238
 
231
239
  $log.info("Sorting #{$items.count} items by #{sort}, reverse #{$opts[:reverse].inspect}")
232
240
 
233
- $items.sort_by! { |i| [i[sort[0]], i[sort[1]], i[sort[2]] ] }
241
+ $items.sort_by! { |i| [ i[sort[0]], i[sort[1]], i[sort[2]] ] }
234
242
  $items.reverse! if $opts[:reverse]
235
243
  rescue Exception => e
236
244
  $log.info("Sorting failed: #{e}\n")
@@ -282,7 +290,7 @@ unless mu
282
290
  when 'remind' then items.map { |i|
283
291
  "REM #{i['sdate'].strftime('%F AT %R')} " +
284
292
  "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
285
- "MSG #{i['title']}"
293
+ "MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] } }"
286
294
  }.join("\n")
287
295
  else abort "No formatter for #{$opts[:output]}"
288
296
  end
@@ -327,8 +335,8 @@ items.each_with_index do |i, j|
327
335
  value = i[prop]
328
336
 
329
337
  next unless value
330
- next if Array === value && !value[0]
331
- next if String === value && value.empty?
338
+ next if value.is_a?(Array) && !value[0]
339
+ next if value.is_a?(String) && value.empty?
332
340
 
333
341
  $log.debug("#{prop}: #{value}")
334
342
 
data/bin/icalpal CHANGED
@@ -191,7 +191,7 @@ $rows.each do |row|
191
191
  end
192
192
  end
193
193
 
194
- if ICalPal::Event === item
194
+ if item.is_a?(ICalPal::Event)
195
195
  # Check for all-day and cancelled events
196
196
  next if $opts[:ea] && item['all_day'].positive?
197
197
  next if $opts[:ia] && !item['all_day'].positive?
@@ -201,10 +201,18 @@ $rows.each do |row|
201
201
  item.recurring.each { |j| add(j) } :
202
202
  item.non_recurring.each { |j| add(j) }
203
203
  else
204
- # Check for dated reminders
205
- if ICalPal::Reminder === item
204
+ # Check for completed or dated reminders
205
+ if item.is_a?(ICalPal::Reminder)
206
+ next if $opts[:ed] && item['completed'].zero?
207
+ next unless $opts[:id] || item['completed'].zero?
208
+
206
209
  next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
207
210
  next if $opts[:dated] == 2 && (!item['due_date'] || item['due_date'].zero?)
211
+
212
+ if $opts[:dated] == 3
213
+ next unless item['due_date']
214
+ next unless item['due_date'].between?($opts[:from].to_i, $opts[:to].to_i)
215
+ end
208
216
  end
209
217
 
210
218
  add(item)
@@ -230,7 +238,7 @@ begin
230
238
 
231
239
  $log.info("Sorting #{$items.count} items by #{sort}, reverse #{$opts[:reverse].inspect}")
232
240
 
233
- $items.sort_by! { |i| [i[sort[0]], i[sort[1]], i[sort[2]] ] }
241
+ $items.sort_by! { |i| [ i[sort[0]], i[sort[1]], i[sort[2]] ] }
234
242
  $items.reverse! if $opts[:reverse]
235
243
  rescue Exception => e
236
244
  $log.info("Sorting failed: #{e}\n")
@@ -282,7 +290,7 @@ unless mu
282
290
  when 'remind' then items.map { |i|
283
291
  "REM #{i['sdate'].strftime('%F AT %R')} " +
284
292
  "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
285
- "MSG #{i['title']}"
293
+ "MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] } }"
286
294
  }.join("\n")
287
295
  else abort "No formatter for #{$opts[:output]}"
288
296
  end
@@ -327,8 +335,8 @@ items.each_with_index do |i, j|
327
335
  value = i[prop]
328
336
 
329
337
  next unless value
330
- next if Array === value && !value[0]
331
- next if String === value && value.empty?
338
+ next if value.is_a?(Array) && !value[0]
339
+ next if value.is_a?(String) && value.empty?
332
340
 
333
341
  $log.debug("#{prop}: #{value}")
334
342
 
data/lib/ToICalPal.rb CHANGED
@@ -74,17 +74,14 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
74
74
  #
75
75
  # @param _arg [Array] Ignored
76
76
  def accept_list_start(_arg)
77
- begin
78
- return if @item['placeholder']
77
+ return if @opts[:nb] || @item['placeholder']
79
78
 
80
- if (@item['due_date'] + ICalPal::ITIME).between?(ICalPal::ITIME + 1, $now.to_i)
81
- @res << "#{@opts[:ab]} " unless @opts[:nb]
82
- return
83
- end
84
- rescue
79
+ if @item['due_date'] && (@item['due_date']).between?(0, $now.to_i)
80
+ # Use alert bullet for overdue items
81
+ @res << "#{@opts[:ab]} "
82
+ else
83
+ @res << "#{@opts[:bullet]} "
85
84
  end
86
-
87
- @res << "#{@opts[:bullet]} " unless @opts[:nb]
88
85
  end
89
86
 
90
87
  # Add a property name
data/lib/defaults.rb CHANGED
@@ -64,6 +64,15 @@ $defaults = {
64
64
  sort: 'prio',
65
65
  },
66
66
 
67
+ tasksDueBefore: {
68
+ dated: 3,
69
+ db: [ ICalPal::Reminder::DB_PATH ],
70
+ iep: %w[ title notes due priority ],
71
+ ps: [ "\n " ],
72
+ sort: 'prio',
73
+ to: $today,
74
+ },
75
+
67
76
  accounts: {
68
77
  iep: %w[ account type ],
69
78
  sort: 'account',
data/lib/event.rb CHANGED
@@ -78,7 +78,7 @@ module ICalPal
78
78
  'sdate' => obj,
79
79
  'placeholder' => true,
80
80
  'title' => 'Nothing.',
81
- } if DateTime === obj
81
+ } if obj.is_a?(DateTime)
82
82
 
83
83
  super
84
84
 
data/lib/icalPal.rb CHANGED
@@ -88,14 +88,17 @@ module ICalPal
88
88
  obj['symbolic_color_name'] ||= type[:color]
89
89
  end
90
90
 
91
- # Create a new CSV::Row with values from +self+. Newlines are
92
- # replaced with '\n' to ensure each Row is a single line of text.
91
+ # Create a new CSV::Row with values from +self+. Control characters
92
+ # are escaped to ensure they are not interpreted by the terminal.
93
93
  #
94
94
  # @param headers [Array] Key names used as the header row in a CSV::Table
95
95
  # @return [CSV::Row] The +Store+, +Calendar+, +CalendarItem+, or
96
96
  # +Reminder+ as a CSV::Row
97
97
  def to_csv(headers)
98
- values = headers.map { |h| (@self[h].respond_to?(:gsub))? @self[h].gsub("\n", '\n') : @self[h] }
98
+ values = headers.map do |h|
99
+ (@self[h].respond_to?(:gsub))?
100
+ @self[h].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] } : @self[h]
101
+ end
99
102
 
100
103
  CSV::Row.new(headers, values)
101
104
  end
data/lib/options.rb CHANGED
@@ -45,6 +45,10 @@ module ICalPal
45
45
  @op.on('%s%s %sPrint events occurring between present time and midnight' % pad('eventsRemaining'))
46
46
  @op.on('%s%s %sPrint tasks with a due date' % pad('datedTasks'))
47
47
  @op.on('%s%s %sPrint tasks with no due date' % pad('undatedTasks'))
48
+ @op.on('%s%s %sPrint uncompleted tasks due between the given dates' % pad('tasksDueBefore'))
49
+ @op.separator('')
50
+ @op.separator("#{@op.summary_indent}stores can be used instead of accounts")
51
+ @op.separator("#{@op.summary_indent}reminders can be used instead of tasks")
48
52
 
49
53
  # global
50
54
  @op.separator("\nGlobal options:\n\n")
@@ -78,6 +82,10 @@ module ICalPal
78
82
  @op.on('--il=LISTS', Array, 'List of reminder lists to include')
79
83
  @op.on('--el=LISTS', Array, 'List of reminder lists to exclude')
80
84
 
85
+ @op.separator('')
86
+ @op.on('--id', 'Include completed reminders')
87
+ @op.on('--ed', 'Exclude uncompleted reminders')
88
+
81
89
  @op.separator('')
82
90
  @op.on('--match=FIELD=REGEX', String, 'Include only items whose FIELD matches REGEX (ignoring case)')
83
91
 
@@ -273,9 +281,12 @@ module ICalPal
273
281
  .merge(env)
274
282
  .merge(cli)
275
283
 
276
- # datedTasks and undatedTasks
277
- opts[:cmd] = 'tasks' if opts[:cmd] == 'datedTasks'
278
- opts[:cmd] = 'tasks' if opts[:cmd] == 'undatedTasks'
284
+ # Other tasks commands
285
+ if opts[:cmd] == 'tasksDueBefore'
286
+ opts.delete(:days) unless opts[:days]
287
+ opts[:from] = RDT.from_epoch(0) unless opts[:from]
288
+ end
289
+ opts[:cmd] = 'tasks' if %w[ datedTasks undatedTasks tasksDueBefore ].include? opts[:cmd]
279
290
 
280
291
  # Make sure opts[:db] and opts[:tasks] are Arrays
281
292
  opts[:db] = [ opts[:db] ] unless opts[:db].is_a?(Array)
@@ -326,6 +337,8 @@ module ICalPal
326
337
  raise(OptionParser::InvalidArgument, '--li cannot be negative') if opts[:li].negative?
327
338
  raise(OptionParser::InvalidOption, 'Start date must be before end date') if opts[:from] && opts[:from] > opts[:to]
328
339
  raise(OptionParser::MissingArgument, 'No properties to display') if opts[:props].empty?
340
+ raise(OptionParser::InvalidArgument, 'Cannot use remind output with tasks') if opts[:cmd] == 'tasks' &&
341
+ opts[:output] == 'remind'
329
342
 
330
343
  rescue StandardError => e
331
344
  @op.abort("#{e}\n\n#{@op.help}\n#{e}")
@@ -335,7 +348,7 @@ module ICalPal
335
348
  end
336
349
 
337
350
  # Commands that can be run
338
- COMMANDS = %w[events eventsToday eventsNow eventsRemaining tasks datedTasks undatedTasks calendars accounts].freeze
351
+ COMMANDS = %w[events eventsToday eventsNow eventsRemaining tasks datedTasks undatedTasks tasksDueBefore calendars accounts].freeze
339
352
 
340
353
  # Supported output formats
341
354
  OUTFORMATS = %w[ansi csv default hash html json md rdoc remind toc xml yaml].freeze
data/lib/reminder.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  r 'timezone'
2
2
 
3
- # Images
4
- # Section
5
-
6
3
  module ICalPal
7
4
  # Class representing items from the <tt>Reminders</tt> database
8
5
  class Reminder
@@ -56,7 +53,7 @@ module ICalPal
56
53
  when 'name', 'reminder', 'task' # Aliases
57
54
  @self['title']
58
55
 
59
- else @self[k]
56
+ else super
60
57
  end
61
58
  end
62
59
 
@@ -90,18 +87,16 @@ module ICalPal
90
87
 
91
88
  @self['long_priority'] = LONG_PRIORITY[@self['prio']] if @self['prio']
92
89
 
93
- # For sorting
94
- @self['sdate'] = (@self['title'])? @self['title'] : ''
95
-
96
90
  # Due date
97
91
  if @self['due_date']
98
92
  begin
93
+ @self['due_date'] += ITIME
99
94
  zone = Timezone.fetch(@self['timezone'])
100
95
  rescue Timezone::Error::InvalidZone
101
96
  zone = '+00:00'
102
97
  end
103
98
 
104
- @self['due'] = RDT.from_time(Time.at(@self['due_date'] + ITIME, in: zone))
99
+ @self['due'] = RDT.from_time(Time.at(@self['due_date'], in: zone))
105
100
  end
106
101
 
107
102
  # Notes
@@ -165,12 +160,14 @@ r1.zContactHandles as messaging,
165
160
  r1.zDueDateDeltaAlertsData as alert,
166
161
  r1.zTimezone as timezone,
167
162
  r1.zckIdentifier as id,
163
+ r1.zCompleted as completed,
168
164
 
169
165
  bl1.zBadgeEmblem as badge,
170
166
  bl1.zColor as color,
171
167
  bl1.zName as list_name,
172
168
  bl1.zParentList as parent,
173
169
  bl1.zSharingStatus as shared,
170
+ bl1.zShouldCategorizeGroceryItems as grocery,
174
171
 
175
172
  -- section members
176
173
  json(bl1.ZMembershipsOfRemindersInSectionsAsData) -> '$.memberships' AS members,
@@ -236,8 +233,7 @@ FROM zremcdReminder r1
236
233
 
237
234
  LEFT OUTER JOIN zremcdBaseList bl1 ON r1.zList = bl1.z_pk
238
235
 
239
- WHERE r1.zCompleted = 0
240
- AND r1.zMarkedForDeletion = 0
236
+ WHERE r1.zMarkedForDeletion = 0
241
237
 
242
238
  SQL
243
239
 
data/lib/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module ICalPal
2
2
  NAME = 'icalPal'.freeze
3
- VERSION = '3.8.3'.freeze
3
+ VERSION = '3.9.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.3
4
+ version: 3.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-08 00:00:00.000000000 Z
10
+ date: 2025-08-23 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |
13
13
  Inspired by icalBuddy and maintains close compatability. Includes