icalPal 3.10.0 → 3.10.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: 014f8b5179cd56cb757404df75a3d23145907d2f40b4186031ed5028ad5e92af
4
- data.tar.gz: 8abe6a5c80c695fa61b0000ae20c52fcbd1fab29ab326fcb89fe1a4d469fae82
3
+ metadata.gz: a19cb126e559cf9684d82d4d47eebfefe1a058dadc63ca642693d1ac488a97b0
4
+ data.tar.gz: 562f98a62d93eda9a03de31a5d1a09a7ddf9841d97abe0ea7d335a0976625dc7
5
5
  SHA512:
6
- metadata.gz: 24bafd17756e9e4ccce28dc0b23ea421d334f3d9c58f3fe62dd07cf26777b47c56587a52e61c5995a730b823a3ad2f0ebc8bef460205ebcde981df3e0bd35108
7
- data.tar.gz: a21caa7650b168542625a176909e05e0f87825c452015e7292f5297aa8b9d96863dc22c1f921a197a97769c9b3fb5db0a593adf1b22784b1874e03ecb05a8b34
6
+ metadata.gz: 659550c0e455ff95a565dbc7d2af162872e14126b35f50f7c8cbaee43c154252d2ef45748421540ab2bc7bde89e50aba0135280a093d086a37e3df970adefd89
7
+ data.tar.gz: 941c218eef0998cc5356dac3e23cd91ea00046f6463b01e0bf6ebb651f008ce00c615d3c787945ec707a9fda35cdb25e733f10058464234ccb93d092213d727e
data/bin/icalPal CHANGED
@@ -206,10 +206,10 @@ $rows.each do |row|
206
206
  next if $opts[:ed] && item['completed'].zero?
207
207
  next unless $opts[:id] || item['completed'].zero?
208
208
 
209
- next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
210
- next if $opts[:dated] == 2 && (!item['due_date'] || item['due_date'].zero?)
209
+ next if $opts[:dated] == 'undatedTasks' && item['due_date'] && item['due_date'].positive?
210
+ next if $opts[:dated] == 'datedTasks' && (!item['due_date'] || item['due_date'].zero?)
211
211
 
212
- if $opts[:dated] == 3
212
+ if $opts[:dated] == 'tasksDueBefore'
213
213
  next unless item['due_date']
214
214
  next unless item['due_date'].between?($opts[:from].to_i, $opts[:to].to_i)
215
215
  end
data/ext/extconf.rb CHANGED
@@ -7,7 +7,7 @@ begin
7
7
  Gem.path.each { |p| gemdir = p if File.writable? p }
8
8
 
9
9
  # Dependencies common to all environments
10
- dependencies = %w[ plist tzinfo ]
10
+ dependencies = %w[ plist ]
11
11
 
12
12
  if RUBY_VERSION >= '3.4'
13
13
  # bigdecimal is not part of the default gems starting from Ruby 3.4.0.
data/lib/ToICalPal.rb CHANGED
@@ -97,7 +97,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
97
97
 
98
98
  # Add a blank line
99
99
  #
100
- # @param *_arg [Array] Ignored
100
+ # @param _arg [Array] Ignored
101
101
  def accept_blank_line(*_arg)
102
102
  @res << "\n"
103
103
  end
data/lib/event.rb CHANGED
@@ -95,7 +95,7 @@ module ICalPal
95
95
 
96
96
  # Save as seconds, Time, RDT
97
97
  ctime = obj[k] + ITIME
98
- ctime -= Time.at(ctime).utc_offset if obj["#{k}_tz"] == '_float' && !zone
98
+ ctime -= Time.at(ctime).utc_offset if obj["#{k.split('_')[0]}_tz"] == '_float' && !zone
99
99
  ttime = Time.at(ctime, in: zone)
100
100
 
101
101
  @self["#{k[0]}seconds"] = ctime
@@ -265,7 +265,7 @@ module ICalPal
265
265
  c['edate'] = ICalPal.nth(nth, day, nedate)
266
266
  else
267
267
  %w[ sdate edate ].each do |d|
268
- diff = day - c['sdate'].wday
268
+ diff = day - c[d].wday
269
269
  diff += 7 if diff.negative?
270
270
 
271
271
  t1 = Time.at(c[d].to_time)
data/lib/options.rb CHANGED
@@ -275,15 +275,9 @@ module ICalPal
275
275
 
276
276
  # Handle tasks command variants
277
277
  if cli[:cmd] =~ /tasks/i
278
- case cli[:cmd]
279
- when 'undatedTasks'
280
- cli[:dated] = 1
278
+ cli[:dated] = cli[:cmd]
281
279
 
282
- when 'datedTasks'
283
- cli[:dated] = 2
284
-
285
- when 'tasksDueBefore'
286
- cli[:dated] = 3
280
+ if cli[:dated] == 'tasksDueBefore'
287
281
  cli.delete(:days) unless cli[:days]
288
282
  cli[:from] = RDT.from_epoch(0) unless cli[:from]
289
283
  cli[:to] = $today unless cli[:to]
data/lib/rdt.rb CHANGED
@@ -25,15 +25,25 @@ module ICalPal
25
25
  # @return [RDT] a new RDT
26
26
  def self.conv(str)
27
27
  case str
28
- when 'yesterday' then $today - 1
28
+ when 'yesterday' then $today.add(-1)
29
29
  when 'today' then $today
30
- when 'tomorrow' then $today + 1
30
+ when 'tomorrow' then $today.add(1)
31
31
  when /^\+([0-9]+)/ then $today + Regexp.last_match(1).to_i
32
32
  when /^-([0-9]+)/ then $today - Regexp.last_match(1).to_i
33
33
  else parse(str)
34
34
  end
35
35
  end
36
36
 
37
+ # Add a number of days accounting for daylight saving time changes
38
+ #
39
+ # @param days [Integer] Number of days to add
40
+ # @return [RDT] A new RDT
41
+ def add(days)
42
+ n = self + days
43
+ t = Time.parse("#{n.year}-#{n.month}-#{n.day} #{n.hour}:#{n.min}:#{n.sec}")
44
+ RDT.from_time(t)
45
+ end
46
+
37
47
  # Values can be +day before yesterday+, +yesterday+,
38
48
  # +today+, +tomorrow+, +day after tomorrow+, or the result from
39
49
  # strftime
@@ -41,10 +51,10 @@ module ICalPal
41
51
  # @return [String] A string representation of self relative to
42
52
  # today.
43
53
  def to_s
44
- return strftime($opts[:df]) if $opts && $opts[:nrd] && $opts[:df]
54
+ return strftime($opts[:df]) if $opts && $opts[:df] && $opts[:nrd]
45
55
  return super unless $today && $opts
46
56
 
47
- case Integer(RDT.new(*ymd, month, day) - $today)
57
+ case (self - $today).round
48
58
  when -2 then 'day before yesterday'
49
59
  when -1 then 'yesterday'
50
60
  when 0 then 'today'
@@ -68,13 +78,13 @@ module ICalPal
68
78
  to_time.to_i
69
79
  end
70
80
 
71
- # @param [Integer] Optional UTC offset
81
+ # @param z [Integer] Optional UTC offset
72
82
  # @return [RDT] Self at 00:00:00
73
83
  def day_start(z = zone)
74
84
  RDT.new(year, month, day, 0, 0, 0, z)
75
85
  end
76
86
 
77
- # @param [Integer] Optional UTC offset
87
+ # @param z [Integer] Optional UTC offset
78
88
  # @return [RDT] Self at 23:59:59
79
89
  def day_end(z = zone)
80
90
  RDT.new(year, month, day, 23, 59, 59, z)
@@ -90,9 +100,5 @@ module ICalPal
90
100
  [ hour, min, sec ]
91
101
  end
92
102
 
93
- # @return [Array] Only the year, month and day of self
94
- def ymd
95
- [ year, month, day ]
96
- end
97
103
  end
98
104
  end
data/lib/reminder.rb CHANGED
@@ -1,5 +1,3 @@
1
- r 'tzinfo'
2
-
3
1
  module ICalPal
4
2
  # Class representing items from the <tt>Reminders</tt> database
5
3
  class Reminder
@@ -103,14 +101,9 @@ module ICalPal
103
101
 
104
102
  # Due date
105
103
  if @self['due_date']
106
- begin
107
- @self['due_date'] += ITIME
108
- zone = TZInfo::Timezone.get(@self['timezone'])
109
- rescue TZInfo::InvalidTimezoneIdentifier
110
- zone = '+00:00'
111
- end
112
-
113
- @self['due'] = RDT.from_time(Time.at(@self['due_date'], in: zone))
104
+ @self['due_date'] += ITIME
105
+ @self['due_date'] -= obj['utc_offset'] if obj['utc_offset']
106
+ @self['due'] = RDT.from_time(Time.at(@self['due_date']))
114
107
  end
115
108
 
116
109
  # Notes
@@ -196,9 +189,9 @@ r1.zNotes as notes,
196
189
  r1.zPriority as priority,
197
190
  r1.zContactHandles as messaging,
198
191
  r1.zDueDateDeltaAlertsData as alert,
199
- r1.zTimezone as timezone,
200
192
  r1.zckIdentifier as id,
201
193
  r1.zCompleted as completed,
194
+ r1.zDisplayDateUpdatedForSecondsFromGMT as utc_offset,
202
195
 
203
196
  bl1.zBadgeEmblem as badge,
204
197
  bl1.zColor as color,
data/lib/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module ICalPal
2
2
  NAME = 'icalPal'.freeze
3
- VERSION = '3.10.0'.freeze
3
+ VERSION = '3.10.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
@@ -22,7 +22,6 @@ extra_rdoc_files:
22
22
  files:
23
23
  - README.md
24
24
  - bin/icalPal
25
- - bin/icalpal
26
25
  - ext/extconf.rb
27
26
  - icalPal.gemspec
28
27
  - lib/EventKit.rb
@@ -62,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
61
  - !ruby/object:Gem::Version
63
62
  version: '0'
64
63
  requirements: []
65
- rubygems_version: 3.7.2
64
+ rubygems_version: 3.6.9
66
65
  specification_version: 4
67
66
  summary: Command-line tool to query the macOS Calendar and Reminders
68
67
  test_files: []
data/bin/icalpal DELETED
@@ -1,363 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # rubocop: disable Style/RedundantBegin
4
-
5
- # require a gem
6
- #
7
- # @param gem [String] The gem
8
- def r(gem)
9
- begin
10
- # puts "require \"#{gem}\""
11
- require gem
12
- rescue LoadError => e
13
- $stderr.puts "FATAL: icalPal is missing a dependency: #{gem}"
14
- $stderr.puts e
15
- $stderr.puts
16
- abort "Try installing with 'gem install #{gem}'"
17
- end
18
- end
19
-
20
- # require_relative a library
21
- #
22
- # @param library [String] The library
23
- def rr(library)
24
- begin
25
- # puts "require_relative \"../lib/#{library}\""
26
- require_relative "../lib/#{library}"
27
- rescue LoadError => e
28
- $stderr.puts "FATAL: Could not load library: #{library}"
29
- $stderr.puts
30
- abort e.message
31
- end
32
- end
33
-
34
- # rubocop: enable Style/RedundantBegin
35
-
36
- %w[ logger csv json rdoc sqlite3 yaml ].each { |g| r g }
37
- %w[ icalPal defaults options utils ].each { |l| rr l }
38
-
39
-
40
- ##################################################
41
- # Load options
42
-
43
- # All kids love log!
44
- $log = Logger.new(STDERR, { level: $defaults[:common][:debug] })
45
- $log.formatter = proc do |s, t, _p, m| # Severity, time, progname, msg
46
- format("[%-5<sev>s] %<time>s [%<file>s:%<line>5s] - %<message>s\n",
47
- {
48
- sev: s,
49
- time: t.strftime('%H:%M:%S.%L'),
50
- file: caller(4, 1)[0].split('/')[-1].split(':')[0],
51
- line: caller(4, 1)[0].split('/')[-1].split(':')[1],
52
- message: m
53
- })
54
- end
55
-
56
- $opts = ICalPal::Options.new.parse_options
57
-
58
- $rows = [] # Rows from the database
59
- $sections = [] # Calendar list sections
60
- $items = [] # Items to be printed
61
-
62
-
63
- ##################################################
64
- # All kids love log!
65
-
66
- $log.info("Options:\n#{$opts.to_json}")
67
-
68
-
69
- ##################################################
70
- # Add an item to the list
71
- #
72
- # @param item[Object]
73
-
74
- def add(item)
75
- $log.debug("Adding #{item.dump} #{item['UUID']} (#{item['title']})") if item['UUID']
76
-
77
- $items.push(item)
78
- end
79
-
80
-
81
- ##################################################
82
- # Load the data
83
-
84
- # What are we getting?
85
- klass = ICalPal.call($opts[:cmd])
86
- success = false
87
-
88
- # Get it
89
- $opts[:db].each do |db|
90
- $log.debug("Trying #{db}")
91
-
92
- if klass == ICalPal::Reminder
93
- # Load all .sqlite files
94
- $log.debug("Loading *.sqlite in #{db}")
95
- Dir.glob("#{db}/*.sqlite").each do |d|
96
- success = true
97
-
98
- rows = klass.load_data(d, klass::QUERY)
99
- $rows += rows
100
-
101
- sections = klass.load_data(d, klass::SECTIONS_QUERY)
102
- $sections += sections
103
-
104
- $log.info("Loaded #{rows.length} rows and #{sections.length} sections from #{d}")
105
- end
106
- else
107
- # Load database
108
- rows = ICalPal.load_data(db, klass::QUERY)
109
- $rows += rows
110
-
111
- success = true
112
-
113
- $log.info("Loaded #{rows.length} rows from #{db}")
114
- end
115
-
116
- rescue Errno::EPERM
117
- # Probably need Full Disk Access
118
-
119
- rescue SQLite3::CantOpenException
120
- # Non-fatal exception, try the next one
121
-
122
- rescue StandardError => e
123
- # Log the error and (try to) continue
124
- $log.error("#{db}: #{e.message}")
125
- end
126
-
127
- # Make sure we opened at least one database
128
- unless success
129
- $log.fatal('Could not open database')
130
-
131
- # SQLite3 does not return useful error messages. If any databases
132
- # failed because of EPERM (operation not permitted), our parent
133
- # process might need Full Disk Access, and we should suggest that.
134
- eperm = 0
135
-
136
- $opts[:db].each do |db|
137
- # Use a real open to get a useful error
138
- File.open(db).close
139
- rescue Exception => e
140
- $log.fatal("#{e.class}: #{db}")
141
-
142
- eperm = 1 if e.instance_of?(Errno::EPERM)
143
- end
144
-
145
- if eperm.positive?
146
- $stderr.puts
147
- $stderr.puts "Does #{ancestor} have Full Disk Access in System Settings?"
148
- $stderr.puts
149
- $stderr.puts "Try running: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'"
150
- end
151
-
152
- abort
153
- end
154
-
155
- $log.debug("Loaded #{$rows.length} #{klass} items")
156
- $log.info("Window is #{$opts[:from]} to #{$opts[:to]}") if $opts[:from]
157
-
158
-
159
- ##################################################
160
- # Process the data
161
-
162
- # Add rows
163
- $rows.each do |row|
164
- # --es/--is
165
- next if $opts[:es].any? row['account']
166
- next unless $opts[:is].empty? || ($opts[:is].any? row['account'])
167
-
168
- # --ec/--ic
169
- unless klass == ICalPal::Store || !row['calendar']
170
- next if $opts[:ec].any? row['calendar']
171
- next unless $opts[:ic].empty? || ($opts[:ic].any? row['calendar'])
172
- end
173
-
174
- # Instantiate an item
175
- item = klass.new(row)
176
-
177
- # --et/--it
178
- next if $opts[:et].any? item['type']
179
- next unless $opts[:it].empty? || ($opts[:it].any? item['type'])
180
-
181
- # --el/--il
182
- next if $opts[:el].any? item['list_name']
183
- next unless $opts[:il].empty? || ($opts[:il].any? item['list_name'])
184
-
185
- # --match
186
- if $opts[:match]
187
- r = $opts[:match].split('=')
188
-
189
- if item[r[0]].to_s.respond_to?(:match)
190
- next unless item[r[0]].to_s =~ Regexp.new(r[1], Regexp::IGNORECASE)
191
- end
192
- end
193
-
194
- if item.is_a?(ICalPal::Event)
195
- # Check for all-day and cancelled events
196
- next if $opts[:ea] && item['all_day'].positive?
197
- next if $opts[:ia] && !item['all_day'].positive?
198
- next if item['status'] == :canceled
199
-
200
- (item['has_recurrences'].positive?)?
201
- item.recurring.each { |j| add(j) } :
202
- item.non_recurring.each { |j| add(j) }
203
- else
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
-
209
- next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
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
216
- end
217
-
218
- add(item)
219
- end
220
- end
221
-
222
- # Add placeholders for empty days
223
- if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
224
- days = $items.collect { |i| i['sday'] }.uniq.sort
225
-
226
- $opts[:days].times do |n|
227
- day = $opts[:from] + n
228
- $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
229
- end
230
- end
231
-
232
- # Sort the rows
233
- begin
234
- $sort_attrs = []
235
- $sort_attrs.push $opts[:sep] if $opts[:sep]
236
- $sort_attrs.push $opts[:sort] if $opts[:sort]
237
- $sort_attrs.push 'sdate'
238
-
239
- $log.info("Sorting #{$items.count} items by #{$sort_attrs}, reverse #{$opts[:reverse].inspect}")
240
-
241
- $items.sort!
242
- $items.reverse! if $opts[:reverse]
243
- rescue Exception => e
244
- $log.info("Sorting failed: #{e}\n")
245
- end
246
-
247
- $log.debug("#{$items.count} items remain")
248
-
249
- # Configure formatting
250
- mu = case $opts[:output]
251
- when 'ansi' then RDoc::Markup::ToAnsi.new
252
- when 'default' then RDoc::Markup::ToICalPal.new($opts)
253
- when 'html'
254
- rdoc = RDoc::Options.new
255
- rdoc.pipe = true
256
- rdoc.output_decoration = false
257
- RDoc::Markup::ToHtml.new(rdoc)
258
- when 'md' then RDoc::Markup::ToMarkdown.new
259
- when 'rdoc' then RDoc::Markup::ToRdoc.new
260
- when 'toc' then RDoc::Markup::ToTableOfContents.new
261
- end
262
-
263
-
264
- ##################################################
265
- # Print the data
266
-
267
- items = $items[0..($opts[:li] - 1)]
268
-
269
- unless mu
270
- $log.debug("Output in #{$opts[:output]} format")
271
-
272
- puts case $opts[:output]
273
- when 'csv'
274
- # Get all headers
275
- headers = []
276
- items.each { |i| headers += i.keys }
277
- headers.uniq!
278
-
279
- # Populate a CSV::Table
280
- table = CSV::Table.new([], headers: headers)
281
- items.each { |i| table << i.to_csv(headers) }
282
-
283
- table
284
- when 'hash' then items.map { |i| i.self }
285
- when 'json' then items.map { |i| i.self }.to_json
286
- when 'xml'
287
- xml = items.map { |i| "<#{$opts[:cmd].chomp('s')}>#{i.to_xml}</#{$opts[:cmd].chomp('s')}>" }
288
- "<#{$opts[:cmd]}>\n#{xml.join}</#{$opts[:cmd]}>"
289
- when 'yaml' then items.map { |i| i.self }.to_yaml
290
- when 'remind' then items.map { |i|
291
- "REM #{i['sdate'].strftime('%F AT %R')} " +
292
- "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
293
- "MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }}"
294
- }.join("\n")
295
- else abort "No formatter for #{$opts[:output]}"
296
- end
297
-
298
- exit
299
- end
300
-
301
- $log.debug("Formatting with #{mu.inspect}")
302
-
303
- doc = RDoc::Markup::Document.new
304
- section = nil
305
-
306
- items.each_with_index do |i, j|
307
- $log.debug("Print #{j}: #{i.inspect}")
308
-
309
- # --li
310
- break if $opts[:li].positive? && j >= $opts[:li]
311
-
312
- # Use RDoc::Markup::Verbatim to save the item
313
- v = RDoc::Markup::Verbatim.new
314
- v.format = i
315
- doc << v
316
-
317
- # Sections
318
- if $opts[:sep] && section != i[$opts[:sep]]
319
- $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
320
-
321
- doc << RDoc::Markup::Raw.new($opts[:sep])
322
-
323
- doc << RDoc::Markup::BlankLine.new if j.positive?
324
- if i[$opts[:sep]]
325
- doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
326
- doc << RDoc::Markup::Rule.new(0)
327
- end
328
-
329
- section = i[$opts[:sep]]
330
- end
331
-
332
- # Item
333
- props = RDoc::Markup::List.new(:BULLET)
334
-
335
- # Properties
336
- $opts[:props].each_with_index do |prop, k|
337
- value = i[prop]
338
-
339
- next unless value
340
- next if value.is_a?(Array) && !value[0]
341
- next if value.is_a?(String) && value.empty?
342
-
343
- $log.debug("#{prop}: #{value}")
344
-
345
- # Use Raw to save the property
346
- props << RDoc::Markup::Raw.new(prop)
347
-
348
- if k.positive?
349
- props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
350
- props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless (i['placeholder'])
351
- else
352
- # First property, value only
353
- props << RDoc::Markup::Heading.new(2, value.to_s)
354
- end
355
- end
356
-
357
- # Print it
358
- props << RDoc::Markup::BlankLine.new unless props.empty?
359
-
360
- doc << props
361
- end
362
-
363
- print doc.accept(mu)