icalPal 2.2.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76513daaf66f3c4aa8b2a9e1703f275ba5d5a65ef9e8b71a86b263c59d409fdd
4
- data.tar.gz: c06430cfbdd5c415dae2d154b774c1ba1feb210534e6511f9fbb1cf12052cbfd
3
+ metadata.gz: c3e477a80eaaf8149b68b6c36aa0a5a9422e4ea9d6fdbcf36b11bdb74f8cca7f
4
+ data.tar.gz: 416f7f1afb820b969d887cd15f0a60ae0344ea5433f6b7a145a4672f166286de
5
5
  SHA512:
6
- metadata.gz: 290f75e744ea88673f474c478565ec281a2b8eae8dcfc730ccb7abf88f33b5fdc8c10baf53b971c336d48a3a4abc3f311e02973e9f2295fea2d134e3d987a499
7
- data.tar.gz: af3d7eec48d408f2c7ff15af7cdddcf581fa60574e1ab3602efb32e6625bfa61833919194108fe012fbae35a312c14af6ffbf628be4921d7cd11651e67717151
6
+ metadata.gz: 9818532d5d53e090e7045480455ceb8f989c4530665698382781d59fce4ab380517cd22c9b204b0a556a87ce263e6242c3a11436112b7a32c121477b8afbd7e7
7
+ data.tar.gz: f16c07f9b1e4d113c096dfed547565114352e3c50ea65bbe49ee58ae9753d8bf2edd0362e5447bb2b529a7038e2c2a70797e63a3793f45e02d3a96914d8ea6b1
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- [![Gem Version](https://badge.fury.io/rb/icalPal.svg)](https://badge.fury.io/rb/icalPal)
1
+ [![Gem Version](https://badge.fury.io/rb/icalpal.svg)](https://badge.fury.io/rb/icalpal)
2
2
 
3
- # icalPal
3
+ # icalpal
4
4
 
5
5
  ## Description
6
6
 
7
- icalPal is a command-line tool to query a macOS Calendar and Reminders
7
+ icalpal is a command-line tool to query a macOS Calendar and Reminders
8
8
  databases for accounts, calendars, events, and tasks. It can be run
9
9
  on any system with [Ruby](https://www.ruby-lang.org/) and access to a
10
10
  Calendar or Reminders database.
@@ -14,27 +14,27 @@ Calendar or Reminders database.
14
14
  As a system-wide Ruby gem:
15
15
 
16
16
  ```
17
- gem install icalPal
17
+ gem install icalpal
18
18
  ```
19
19
 
20
20
  or in your home diretory:
21
21
 
22
22
  ```
23
- gem install --user-install icalPal
23
+ gem install --user-install icalpal
24
24
  ```
25
25
 
26
26
  As a Homebrew formula:
27
27
 
28
28
  ```
29
- brew tap ajrosen/icalPal
30
- brew install icalPal
29
+ brew tap ajrosen/icalpal
30
+ brew install icalpal
31
31
  ```
32
32
 
33
33
  ## Features
34
34
 
35
35
  ### Compatability with [icalBuddy](https://github.com/ali-rantakari/icalBuddy)
36
36
 
37
- icalPal tries to be compatible with icalBuddy for command-line options
37
+ icalpal tries to be compatible with icalBuddy for command-line options
38
38
  and for output. There are a some important differences to be aware
39
39
  of.
40
40
 
@@ -48,17 +48,17 @@ of.
48
48
 
49
49
  ### Additional commands
50
50
 
51
- ```icalPal accounts```
51
+ ```icalpal accounts```
52
52
 
53
- Shows a list of enabled Calendar accounts. Internally they are known as *Stores*; you can run ```icalPal stores``` instead.
53
+ Shows a list of enabled Calendar accounts. Internally they are known as *Stores*; you can run ```icalpal stores``` instead.
54
54
 
55
- ```icalPal datedTasks```
55
+ ```icalpal datedTasks```
56
56
 
57
57
  Shows only reminders that have a due date.
58
58
 
59
59
  ### Additional options
60
60
 
61
- * 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```.
61
+ * 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```.
62
62
  * The ```-c``` part is optional, but you cannot abbreviate the command if you leave it off.
63
63
  * Use ```-o``` to print the output in different formats. CSV or JSON are intertesting choices.
64
64
  * Copy your Calendar database file and use ```--db``` on it.
@@ -68,12 +68,13 @@ Shows only reminders that have a due date.
68
68
  * ```--aep``` is like ```--iep```, but *adds* to the default property list instead of replacing it.
69
69
  * ```--sep``` to separate by any property, not just calendar (```--sc```) or date (```--sd```)
70
70
  * ```--color``` uses a wider color palette. Calendar colors are what you have chosen in the Calendar app. Not supported in all terminals, but looks great in [iTerm2](https://iterm2.com/).
71
+ * ```--match``` lets you filter the results of any command to items where a *FIELD* matches a regular expression. Eg., ```--match notes=zoom.us``` to show only Zoom meeetings
71
72
 
72
- Because icalPal is written in Ruby, and not a native Mac application, you can run it just about anywhere. It's been tested with the version of Ruby (2.6.10) included with macOS.
73
+ Because icalpal is written in Ruby, and not a native Mac application, you can run it just about anywhere. It's been tested with the version of Ruby (2.6.10) included with macOS.
73
74
 
74
75
  ## Usage
75
76
 
76
- icalPal: Usage: icalPal [options] [-c] COMMAND
77
+ icalpal: Usage: icalpal [options] [-c] COMMAND
77
78
 
78
79
  COMMAND must be one of the following:
79
80
  ```
@@ -95,7 +96,7 @@ Global options:
95
96
  --db=DB Use DB file instead of Calendar (default: /Users/ajr/Library/Calendars/Calendar.sqlitedb)
96
97
  For the tasks commands this should be a directory containing .sqlite files
97
98
  (default: /Users/ajr/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores)
98
- --cf=FILE Set config file path (default: /Users/ajr/.icalPal)
99
+ --cf=FILE Set config file path (default: /Users/ajr/.icalpal)
99
100
  -o, --output=FORMAT Print as FORMAT (default: default)
100
101
  [ansi, csv, default, hash, html, json, md, rdoc, remind, toc, xml, yaml]
101
102
  ```
@@ -114,6 +115,9 @@ Including/excluding calendars and reminders:
114
115
 
115
116
  --il=LISTS List of reminder lists to include
116
117
  --el=LISTS List of reminder lists to exclude
118
+
119
+ --match=FIELD=REGEXP
120
+ Include only items whose FIELD matches REGEXP (ignoring case)
117
121
  ```
118
122
 
119
123
  Choosing dates:
@@ -196,19 +200,19 @@ Environment variables:
196
200
  ```
197
201
  ICALPAL Additional arguments
198
202
  ICALPAL_CONFIG Additional arguments from a file
199
- (default: /Users/ajr/.icalPal)
203
+ (default: /Users/ajr/.icalpal)
200
204
  ```
201
205
 
202
206
  ## Output formats
203
207
 
204
- icalPal supports several output formats. The **default** format tries
208
+ icalpal supports several output formats. The **default** format tries
205
209
  to mimic icalBuddy as much as possible.
206
210
 
207
211
  CSV, Hash, JSON, XML, and YAML print all fields for all items in their
208
212
  respective formats. From that you can analyze the results any way you
209
213
  like.
210
214
 
211
- [Remind](https://dianne.skoll.ca/projects/remind/) format uses a minimal implementation built in icalPal.
215
+ [Remind](https://dianne.skoll.ca/projects/remind/) format uses a minimal implementation built in icalpal.
212
216
 
213
217
  Other formats such as ANSI, HTML, Markdown, RDoc, and TOC, use Ruby's
214
218
  [RDoc::Markup](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup.html)
@@ -256,9 +260,9 @@ Lawton](https://github.com/jimlawton) that it even compiles anymore.
256
260
  Instead of trying to understand and extend the existing code, I chose
257
261
  to start anew using my language of choice: Ruby. Using Ruby meant
258
262
  there is *much* less code; about 1,600 lines vs. 7,000. It also means
259
- icalPal is multi-platform.
263
+ icalpal is multi-platform.
260
264
 
261
265
  I won't pretend to understand **why** you would want to run this on
262
- Linux or Windows. But since icalPal is written in Ruby and gets its
266
+ Linux or Windows. But since icalpal is written in Ruby and gets its
263
267
  data directly from the Calendar and Reminders database files instead
264
268
  of an API, you *can*.
data/bin/icalPal CHANGED
@@ -9,12 +9,12 @@ begin
9
9
  require 'sqlite3'
10
10
  require 'yaml'
11
11
 
12
- require_relative '../lib/icalPal'
12
+ require_relative '../lib/icalpal'
13
13
  require_relative '../lib/options'
14
14
  rescue LoadError => e
15
15
  dep = e.message[/-- (.*)/, 1]
16
16
 
17
- $stderr.puts "FATAL: icalPal is missing a dependency: #{dep}"
17
+ $stderr.puts "FATAL: icalpal is missing a dependency: #{dep}"
18
18
  $stderr.puts
19
19
  $stderr.puts "Install with 'gem install --user-install #{dep}'"
20
20
 
@@ -65,12 +65,19 @@ end
65
65
  klass = ICalPal::($opts[:cmd])
66
66
 
67
67
  # Get it
68
- if klass == ICalPal::Reminder then
69
- # Load all .sqlite files
70
- Dir.glob("#{$opts[:db]}/*.sqlite").each { |db| $rows += ICalPal.load_data(db, klass::QUERY) }
71
- else
72
- # Load database
73
- $rows += ICalPal.load_data($opts[:db], klass::QUERY)
68
+ $opts[:db].each do |db|
69
+ $log.debug("Trying #{db}")
70
+
71
+ if klass == ICalPal::Reminder then
72
+ # Load all .sqlite files
73
+ $log.debug("Loading *.sqlite in #{db}")
74
+ Dir.glob("#{db}/*.sqlite").each { |d| $rows += ICalPal.load_data(d, klass::QUERY) }
75
+ else
76
+ # Load database
77
+ begin
78
+ $rows += ICalPal.load_data(db, klass::QUERY)
79
+ end
80
+ end
74
81
  end
75
82
 
76
83
  $log.info("Loaded #{$rows.count} #{klass} rows")
@@ -95,14 +102,16 @@ $rows.each_with_index do |row, i|
95
102
  end
96
103
 
97
104
  # --ec/--ic
98
- if $opts[:ec].any? row['calendar'] then
99
- $log.debug(":ec")
100
- next
101
- end
105
+ unless klass == ICalPal::Store or !row['calendar']
106
+ if $opts[:ec].any? row['calendar'] then
107
+ $log.debug(":ec")
108
+ next
109
+ end
102
110
 
103
- unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
104
- $log.debug(":ic")
105
- next
111
+ unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
112
+ $log.debug(":ic")
113
+ next
114
+ end
106
115
  end
107
116
 
108
117
  # Instantiate an item
@@ -130,13 +139,13 @@ $rows.each_with_index do |row, i|
130
139
  next
131
140
  end
132
141
 
133
- # --regexp
142
+ # --match
134
143
  if $opts[:match]
135
144
  r = $opts[:match].split('=')
136
145
 
137
146
  if item[r[0]].to_s.respond_to?(:match)
138
147
  unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE)) then
139
- $log.debug(":regex")
148
+ $log.debug(":match")
140
149
  next
141
150
  end
142
151
  end
data/bin/icalpal ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'logger'
5
+
6
+ require 'csv'
7
+ require 'json'
8
+ require 'rdoc'
9
+ require 'sqlite3'
10
+ require 'yaml'
11
+
12
+ require_relative '../lib/icalpal'
13
+ require_relative '../lib/options'
14
+ rescue LoadError => e
15
+ dep = e.message[/-- (.*)/, 1]
16
+
17
+ $stderr.puts "FATAL: icalpal is missing a dependency: #{dep}"
18
+ $stderr.puts
19
+ $stderr.puts "Install with 'gem install --user-install #{dep}'"
20
+
21
+ exit
22
+ end
23
+
24
+
25
+ ##################################################
26
+ # Load options
27
+
28
+ # All kids love log!
29
+ $log = Logger.new(STDERR, { level: $defaults[:common][:debug] })
30
+ $log.formatter = proc do |s, t, p, m| # Severity, time, progname, msg
31
+ ($log.level.positive?)? "#{s}: #{m}\n" :
32
+ "[%-5s] %s [%s] - %s\n" %
33
+ [ s, t.strftime('%H:%M:%S.%L'), caller(4, 1)[0].split('/')[-1], m ]
34
+ end
35
+
36
+ $opts = ICalPal::Options.new.parse_options
37
+
38
+ $rows = [] # Rows from the database
39
+ $items = [] # Items to be printed
40
+
41
+
42
+ ##################################################
43
+ # All kids love log!
44
+
45
+ $log.info("Options: #{$opts}")
46
+
47
+
48
+ ##################################################
49
+ # Add an item to the list
50
+ #
51
+ # @param item[Object]
52
+
53
+ def add(item)
54
+ $log.info("Adding #{item.inspect} #{item['UUID']} (#{item['title']})") if item['UUID']
55
+
56
+ item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if item === ICalPal::Event && item['sdate']
57
+ $items.push(item)
58
+ end
59
+
60
+
61
+ ##################################################
62
+ # Load the data
63
+
64
+ # What are we getting?
65
+ klass = ICalPal::($opts[:cmd])
66
+
67
+ # Get it
68
+ $opts[:db].each do |db|
69
+ $log.debug("Trying #{db}")
70
+
71
+ if klass == ICalPal::Reminder then
72
+ # Load all .sqlite files
73
+ $log.debug("Loading *.sqlite in #{db}")
74
+ Dir.glob("#{db}/*.sqlite").each { |d| $rows += ICalPal.load_data(d, klass::QUERY) }
75
+ else
76
+ # Load database
77
+ begin
78
+ $rows += ICalPal.load_data(db, klass::QUERY)
79
+ end
80
+ end
81
+ end
82
+
83
+ $log.info("Loaded #{$rows.count} #{klass} rows")
84
+
85
+
86
+ ##################################################
87
+ # Process the data
88
+
89
+ # Add rows
90
+ $rows.each_with_index do |row, i|
91
+ $log.debug("Row #{i}: #{row['ROWID']}:#{row['UUID']} - #{row['account']}/#{row['calendar']}/#{row['title']}")
92
+
93
+ # --es/--is
94
+ if $opts[:es].any? row['account'] then
95
+ $log.debug(":es")
96
+ next
97
+ end
98
+
99
+ unless $opts[:is].empty? or $opts[:is].any? row['account']
100
+ $log.debug(":is");
101
+ next
102
+ end
103
+
104
+ # --ec/--ic
105
+ unless klass == ICalPal::Store or !row['calendar']
106
+ if $opts[:ec].any? row['calendar'] then
107
+ $log.debug(":ec")
108
+ next
109
+ end
110
+
111
+ unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
112
+ $log.debug(":ic")
113
+ next
114
+ end
115
+ end
116
+
117
+ # Instantiate an item
118
+ item = klass.new(row)
119
+
120
+ # --et/--it
121
+ if $opts[:et].any? item['type'] then
122
+ $log.debug(":et")
123
+ next
124
+ end
125
+
126
+ unless $opts[:it].empty? or $opts[:it].any? item['type']
127
+ $log.debug(":it")
128
+ next
129
+ end
130
+
131
+ # --el/--il
132
+ if $opts[:el].any? item['list_name'] then
133
+ $log.debug(":el")
134
+ next
135
+ end
136
+
137
+ unless $opts[:il].empty? or $opts[:il].any? item['list_name']
138
+ $log.debug(":il")
139
+ next
140
+ end
141
+
142
+ # --match
143
+ if $opts[:match]
144
+ r = $opts[:match].split('=')
145
+
146
+ if item[r[0]].to_s.respond_to?(:match)
147
+ unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE)) then
148
+ $log.debug(":match")
149
+ next
150
+ end
151
+ end
152
+ end
153
+
154
+ if ICalPal::Event === item
155
+ # Check for all-day and cancelled events
156
+ if $opts[:ea] && item['all_day'].positive? then
157
+ $log.debug(":ea")
158
+ next
159
+ end
160
+
161
+ if $opts[:ia] && !item['all_day'].positive? then
162
+ $log.debug(":ia")
163
+ next
164
+ end
165
+
166
+ if item['status'] == :canceled then
167
+ $log.debug(":canceled")
168
+ next
169
+ end
170
+
171
+ (item['has_recurrences'].positive?)?
172
+ item.recurring.each { |i| add(i) } :
173
+ item.non_recurring.each { |i| add(i) }
174
+ else
175
+ # Check for dated reminders
176
+ if ICalPal::Reminder === item then
177
+ if $opts[:dated] == 1 and item['due_date'] > 0 then
178
+ $log.debug(":undated")
179
+ next
180
+ end
181
+
182
+ if $opts[:dated] == 2 and item['due_date'] == 0 then
183
+ $log.debug(":dated")
184
+ next
185
+ end
186
+ end
187
+
188
+ add(item)
189
+ end
190
+ end
191
+
192
+ # Add placeholders for empty days
193
+ if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
194
+ days = $items.collect { |i| i['sday'] }.uniq.sort
195
+
196
+ $opts[:days].times do |n|
197
+ day = $opts[:from] + n
198
+ $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
199
+ end
200
+ end
201
+
202
+ # Sort the rows
203
+ begin
204
+ $log.info("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
205
+
206
+ $items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
207
+ $items.reverse! if $opts[:reverse]
208
+ $items.uniq!
209
+ rescue Exception => e
210
+ $log.info("Sorting failed: #{e}\n")
211
+ end
212
+
213
+ $log.debug("#{$items.count} items remain")
214
+
215
+ # Configure formatting
216
+ mu = case $opts[:output]
217
+ when 'ansi' then RDoc::Markup::ToAnsi.new
218
+ when 'default' then RDoc::Markup::ToICalPal.new($opts)
219
+ when 'html' then
220
+ rdoc = RDoc::Options.new
221
+ rdoc.pipe = true
222
+ rdoc.output_decoration = false
223
+ RDoc::Markup::ToHtml.new(rdoc)
224
+ when 'md' then RDoc::Markup::ToMarkdown.new
225
+ when 'rdoc' then RDoc::Markup::ToRdoc.new
226
+ when 'toc' then RDoc::Markup::ToTableOfContents.new
227
+ end
228
+
229
+
230
+ ##################################################
231
+ # Print the data
232
+
233
+ items = $items[0..$opts[:li] - 1]
234
+
235
+ unless mu
236
+ $log.debug("Output in #{$opts[:output]} format")
237
+
238
+ puts case $opts[:output]
239
+ when 'csv' then
240
+ # Get all headers
241
+ headers = []
242
+ items.each { |i| headers += i.keys }
243
+ headers.uniq!
244
+
245
+ # Populate a CSV::Table
246
+ table = CSV::Table.new([], headers: headers)
247
+ items.each { |i| table << i.to_csv(headers) }
248
+
249
+ table
250
+ when 'hash' then items.map { |i| i.self }
251
+ when 'json' then items.map { |i| i.self }.to_json
252
+ when 'xml' then
253
+ xml = items.map { |i| "<#{$opts[:cmd].chomp("s")}>#{i.to_xml}</#{$opts[:cmd].chomp("s")}>" }
254
+ "<#{$opts[:cmd]}>\n#{xml.join("")}</#{$opts[:cmd]}>"
255
+ when 'yaml' then items.map { |i| i.self }.to_yaml
256
+ when 'remind' then items.map { |i|
257
+ "REM #{i['sdate'].strftime('%F AT %R')} " +
258
+ "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i } " +
259
+ "MSG #{i['title']}"
260
+ }.join("\n")
261
+ else abort "No formatter for #{$opts[:output]}"
262
+ end
263
+
264
+ exit
265
+ end
266
+
267
+ $log.debug("Formatting with #{mu.inspect}")
268
+
269
+ doc = RDoc::Markup::Document.new
270
+ section = nil
271
+
272
+ items.each_with_index do |i, j|
273
+ $log.debug("Print #{j}: #{i.inspect}")
274
+
275
+ # --li
276
+ break if $opts[:li].positive? && j >= $opts[:li]
277
+
278
+ # Use RDoc::Markup::Verbatim to save the item
279
+ v = RDoc::Markup::Verbatim.new
280
+ v.format = i
281
+ doc << v
282
+
283
+ # Sections
284
+ if $opts[:sep] && section != i[$opts[:sep]]
285
+ $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
286
+
287
+ doc << RDoc::Markup::Raw.new($opts[:sep])
288
+
289
+ doc << RDoc::Markup::BlankLine.new if j.positive?
290
+ doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
291
+ doc << RDoc::Markup::Rule.new(0)
292
+
293
+ section = i[$opts[:sep]]
294
+ end
295
+
296
+ # Item
297
+ props = RDoc::Markup::List.new(:BULLET)
298
+
299
+ # Properties
300
+ $opts[:props].each_with_index do |prop, k|
301
+ value = i[prop]
302
+
303
+ next unless value
304
+ next if Array === value && !value[0]
305
+ next if String === value && value.length == 0
306
+
307
+ $log.debug("#{prop}: #{value}")
308
+
309
+ # Use Raw to save the property
310
+ props << RDoc::Markup::Raw.new(prop)
311
+
312
+ unless k.positive?
313
+ # First property, value only
314
+ props << RDoc::Markup::Heading.new(2, value.to_s)
315
+ else
316
+ props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
317
+ props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless(i['placeholder'])
318
+ end
319
+ end
320
+
321
+ # Print it
322
+ props << RDoc::Markup::BlankLine.new unless props.empty?
323
+
324
+ doc << props
325
+ end
326
+
327
+ print doc.accept(mu)
data/icalPal.gemspec CHANGED
@@ -1,8 +1,9 @@
1
1
  require './lib/version'
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = "icalPal"
4
+ s.name = ICalPal::NAME
5
5
  s.version = ICalPal::VERSION
6
+
6
7
  s.summary = "Command-line tool to query the macOS Calendar"
7
8
  s.description = <<-EOF
8
9
  Inspired by icalBuddy and maintains close compatability. Includes
data/lib/EventKit.rb CHANGED
@@ -42,6 +42,7 @@ class EventKit
42
42
  { name: 'MobileMe', color: '#FFFF00' }, # Yellow
43
43
  { name: 'Subscribed', color: '#FF0000' }, # Red
44
44
  { name: 'Birthdays', color: '#FF00FF' }, # Magenta
45
+ { name: 'Reminders', color: '#066FF3' }, # Blue
45
46
  ]
46
47
 
47
48
  EKSpan = [
data/lib/defaults.rb CHANGED
@@ -8,9 +8,12 @@ $defaults = {
8
8
  ab: '!',
9
9
  aep: [],
10
10
  bullet: '•',
11
- cf: "#{ENV['HOME']}/.icalPal",
11
+ cf: "#{ENV['HOME']}/.icalpal",
12
12
  color: false,
13
- db: "#{ENV['HOME']}/Library/Calendars/Calendar.sqlitedb",
13
+ db: [
14
+ "#{ENV['HOME']}/Library/Calendars/Calendar.sqlitedb",
15
+ "#{ENV['HOME']}/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb",
16
+ ],
14
17
  debug: Logger::WARN,
15
18
  df: '%b %-d, %Y',
16
19
  ec: [],
@@ -36,19 +39,19 @@ $defaults = {
36
39
  },
37
40
  tasks: {
38
41
  dated: 0,
39
- db: ICalPal::Reminder::DB_PATH,
42
+ db: [ ICalPal::Reminder::DB_PATH ],
40
43
  iep: [ 'title', 'notes', 'due', 'priority' ],
41
44
  sort: 'prio',
42
45
  },
43
46
  undatedTasks: {
44
47
  dated: 1,
45
- db: ICalPal::Reminder::DB_PATH,
48
+ db: [ ICalPal::Reminder::DB_PATH ],
46
49
  iep: [ 'title', 'notes', 'due', 'priority' ],
47
50
  sort: 'prio',
48
51
  },
49
52
  datedTasks: {
50
53
  dated: 2,
51
- db: ICalPal::Reminder::DB_PATH,
54
+ db: [ ICalPal::Reminder::DB_PATH ],
52
55
  iep: [ 'title', 'notes', 'due', 'priority' ],
53
56
  sort: 'prio',
54
57
  },
data/lib/event.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  module ICalPal
2
4
  # Class representing items from the <tt>CalendarItem</tt> table
3
5
  class Event
@@ -92,8 +94,10 @@ module ICalPal
92
94
  end
93
95
 
94
96
  if @self['start_tz'] == '_float'
95
- @self['sdate'] = RDT.new(*(@self['sdate'].to_time - Time.zone_offset($now.zone())).to_a.reverse[4..], $now.zone)
96
- @self['edate'] = RDT.new(*(@self['edate'].to_time - Time.zone_offset($now.zone())).to_a.reverse[4..], $now.zone)
97
+ tzoffset = Time.zone_offset($now.zone())
98
+
99
+ @self['sdate'] = RDT.new(*(@self['sdate'].to_time - tzoffset).to_a.reverse[4..], $now.zone)
100
+ @self['edate'] = RDT.new(*(@self['edate'].to_time - tzoffset).to_a.reverse[4..], $now.zone)
97
101
  end
98
102
 
99
103
  # Type of calendar event is from
@@ -112,8 +116,13 @@ module ICalPal
112
116
  def non_recurring
113
117
  events = []
114
118
 
119
+ nDays = (self['duration'] / 86400).to_i
120
+
121
+ # Sanity checks
122
+ return events if nDays > 100000
123
+
115
124
  # Repeat for multi-day events
116
- ((self['duration'] / 86400).to_i + 1).times do |i|
125
+ (nDays + 1).times do |i|
117
126
  break if self['sdate'] > $opts[:to]
118
127
 
119
128
  $log.debug("multi-day event #{i + 1}") if (i > 0)
data/lib/icalPal.rb CHANGED
@@ -58,6 +58,9 @@ module ICalPal
58
58
  rescue SQLite3::BusyException => e
59
59
  $log.error("Non-fatal error closing database #{db.filename}")
60
60
 
61
+ rescue SQLite3::CantOpenException => e
62
+ $log.debug("Can't open #{db_file}")
63
+
61
64
  rescue SQLite3::SQLException => e
62
65
  $log.info("#{db_file}: #{e}")
63
66
 
data/lib/options.rb CHANGED
@@ -13,7 +13,7 @@ module ICalPal
13
13
  #
14
14
  # Many options are intentionally copied from
15
15
  # icalBuddy[https://github.com/ali-rantakari/icalBuddy]. Note that
16
- # icalPal requires two hyphens for all options, except single-letter
16
+ # icalpal requires two hyphens for all options, except single-letter
17
17
  # options which require a single hyphen.
18
18
  #
19
19
  # Options can be abbreviated as long as they are unique.
data/lib/version.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  module ICalPal
2
- VERSION = '2.2.0'
2
+ NAME = 'icalPal'
3
+ VERSION = '3.1.0'
3
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-15 00:00:00.000000000 Z
11
+ date: 2024-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3
@@ -50,6 +50,7 @@ extra_rdoc_files:
50
50
  files:
51
51
  - README.md
52
52
  - bin/icalPal
53
+ - bin/icalpal
53
54
  - icalPal.gemspec
54
55
  - lib/EventKit.rb
55
56
  - lib/ToICalPal.rb
@@ -81,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  - !ruby/object:Gem::Version
82
83
  version: '0'
83
84
  requirements: []
84
- rubygems_version: 3.5.9
85
+ rubygems_version: 3.5.18
85
86
  signing_key:
86
87
  specification_version: 4
87
88
  summary: Command-line tool to query the macOS Calendar