icalPal 2.2.0 → 3.0.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: 25a711f9e4887a81dafccbc57e0a2fa84530dc8cee81e58f0d5df6041783bec2
4
+ data.tar.gz: 698a498065687f7a7d06e6c316d1937bfc3b1a58d7a661b612bb8d39b5429318
5
5
  SHA512:
6
- metadata.gz: 290f75e744ea88673f474c478565ec281a2b8eae8dcfc730ccb7abf88f33b5fdc8c10baf53b971c336d48a3a4abc3f311e02973e9f2295fea2d134e3d987a499
7
- data.tar.gz: af3d7eec48d408f2c7ff15af7cdddcf581fa60574e1ab3602efb32e6625bfa61833919194108fe012fbae35a312c14af6ffbf628be4921d7cd11651e67717151
6
+ metadata.gz: 9cf18231c95a94b663e1a41ecd16326f447fb038864677ad05ba2a6fa5f684ae999732401ff30b9df1b0739ff31f78f7b0385e3f7774d5260b9cfbc37a6d2680
7
+ data.tar.gz: d3aaf37df004da104518e80463c2bc6933ba87277fc9c913bb73a4653055a93417694fb1de655d1c3776f3d47d45afaf241b5ba59361894bafa7ebd1bf253c29
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
 
@@ -95,14 +95,16 @@ $rows.each_with_index do |row, i|
95
95
  end
96
96
 
97
97
  # --ec/--ic
98
- if $opts[:ec].any? row['calendar'] then
99
- $log.debug(":ec")
100
- next
101
- end
98
+ unless klass == ICalPal::Store or !row['calendar']
99
+ if $opts[:ec].any? row['calendar'] then
100
+ $log.debug(":ec")
101
+ next
102
+ end
102
103
 
103
- unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
104
- $log.debug(":ic")
105
- next
104
+ unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
105
+ $log.debug(":ic")
106
+ next
107
+ end
106
108
  end
107
109
 
108
110
  # Instantiate an item
@@ -130,13 +132,13 @@ $rows.each_with_index do |row, i|
130
132
  next
131
133
  end
132
134
 
133
- # --regexp
135
+ # --match
134
136
  if $opts[:match]
135
137
  r = $opts[:match].split('=')
136
138
 
137
139
  if item[r[0]].to_s.respond_to?(:match)
138
140
  unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE)) then
139
- $log.debug(":regex")
141
+ $log.debug(":match")
140
142
  next
141
143
  end
142
144
  end
data/bin/icalpal ADDED
@@ -0,0 +1,320 @@
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
+ 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)
74
+ end
75
+
76
+ $log.info("Loaded #{$rows.count} #{klass} rows")
77
+
78
+
79
+ ##################################################
80
+ # Process the data
81
+
82
+ # Add rows
83
+ $rows.each_with_index do |row, i|
84
+ $log.debug("Row #{i}: #{row['ROWID']}:#{row['UUID']} - #{row['account']}/#{row['calendar']}/#{row['title']}")
85
+
86
+ # --es/--is
87
+ if $opts[:es].any? row['account'] then
88
+ $log.debug(":es")
89
+ next
90
+ end
91
+
92
+ unless $opts[:is].empty? or $opts[:is].any? row['account']
93
+ $log.debug(":is");
94
+ next
95
+ end
96
+
97
+ # --ec/--ic
98
+ unless klass == ICalPal::Store or !row['calendar']
99
+ if $opts[:ec].any? row['calendar'] then
100
+ $log.debug(":ec")
101
+ next
102
+ end
103
+
104
+ unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
105
+ $log.debug(":ic")
106
+ next
107
+ end
108
+ end
109
+
110
+ # Instantiate an item
111
+ item = klass.new(row)
112
+
113
+ # --et/--it
114
+ if $opts[:et].any? item['type'] then
115
+ $log.debug(":et")
116
+ next
117
+ end
118
+
119
+ unless $opts[:it].empty? or $opts[:it].any? item['type']
120
+ $log.debug(":it")
121
+ next
122
+ end
123
+
124
+ # --el/--il
125
+ if $opts[:el].any? item['list_name'] then
126
+ $log.debug(":el")
127
+ next
128
+ end
129
+
130
+ unless $opts[:il].empty? or $opts[:il].any? item['list_name']
131
+ $log.debug(":il")
132
+ next
133
+ end
134
+
135
+ # --match
136
+ if $opts[:match]
137
+ r = $opts[:match].split('=')
138
+
139
+ if item[r[0]].to_s.respond_to?(:match)
140
+ unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE)) then
141
+ $log.debug(":match")
142
+ next
143
+ end
144
+ end
145
+ end
146
+
147
+ if ICalPal::Event === item
148
+ # Check for all-day and cancelled events
149
+ if $opts[:ea] && item['all_day'].positive? then
150
+ $log.debug(":ea")
151
+ next
152
+ end
153
+
154
+ if $opts[:ia] && !item['all_day'].positive? then
155
+ $log.debug(":ia")
156
+ next
157
+ end
158
+
159
+ if item['status'] == :canceled then
160
+ $log.debug(":canceled")
161
+ next
162
+ end
163
+
164
+ (item['has_recurrences'].positive?)?
165
+ item.recurring.each { |i| add(i) } :
166
+ item.non_recurring.each { |i| add(i) }
167
+ else
168
+ # Check for dated reminders
169
+ if ICalPal::Reminder === item then
170
+ if $opts[:dated] == 1 and item['due_date'] > 0 then
171
+ $log.debug(":undated")
172
+ next
173
+ end
174
+
175
+ if $opts[:dated] == 2 and item['due_date'] == 0 then
176
+ $log.debug(":dated")
177
+ next
178
+ end
179
+ end
180
+
181
+ add(item)
182
+ end
183
+ end
184
+
185
+ # Add placeholders for empty days
186
+ if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
187
+ days = $items.collect { |i| i['sday'] }.uniq.sort
188
+
189
+ $opts[:days].times do |n|
190
+ day = $opts[:from] + n
191
+ $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
192
+ end
193
+ end
194
+
195
+ # Sort the rows
196
+ begin
197
+ $log.info("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
198
+
199
+ $items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
200
+ $items.reverse! if $opts[:reverse]
201
+ $items.uniq!
202
+ rescue Exception => e
203
+ $log.info("Sorting failed: #{e}\n")
204
+ end
205
+
206
+ $log.debug("#{$items.count} items remain")
207
+
208
+ # Configure formatting
209
+ mu = case $opts[:output]
210
+ when 'ansi' then RDoc::Markup::ToAnsi.new
211
+ when 'default' then RDoc::Markup::ToICalPal.new($opts)
212
+ when 'html' then
213
+ rdoc = RDoc::Options.new
214
+ rdoc.pipe = true
215
+ rdoc.output_decoration = false
216
+ RDoc::Markup::ToHtml.new(rdoc)
217
+ when 'md' then RDoc::Markup::ToMarkdown.new
218
+ when 'rdoc' then RDoc::Markup::ToRdoc.new
219
+ when 'toc' then RDoc::Markup::ToTableOfContents.new
220
+ end
221
+
222
+
223
+ ##################################################
224
+ # Print the data
225
+
226
+ items = $items[0..$opts[:li] - 1]
227
+
228
+ unless mu
229
+ $log.debug("Output in #{$opts[:output]} format")
230
+
231
+ puts case $opts[:output]
232
+ when 'csv' then
233
+ # Get all headers
234
+ headers = []
235
+ items.each { |i| headers += i.keys }
236
+ headers.uniq!
237
+
238
+ # Populate a CSV::Table
239
+ table = CSV::Table.new([], headers: headers)
240
+ items.each { |i| table << i.to_csv(headers) }
241
+
242
+ table
243
+ when 'hash' then items.map { |i| i.self }
244
+ when 'json' then items.map { |i| i.self }.to_json
245
+ when 'xml' then
246
+ xml = items.map { |i| "<#{$opts[:cmd].chomp("s")}>#{i.to_xml}</#{$opts[:cmd].chomp("s")}>" }
247
+ "<#{$opts[:cmd]}>\n#{xml.join("")}</#{$opts[:cmd]}>"
248
+ when 'yaml' then items.map { |i| i.self }.to_yaml
249
+ when 'remind' then items.map { |i|
250
+ "REM #{i['sdate'].strftime('%F AT %R')} " +
251
+ "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i } " +
252
+ "MSG #{i['title']}"
253
+ }.join("\n")
254
+ else abort "No formatter for #{$opts[:output]}"
255
+ end
256
+
257
+ exit
258
+ end
259
+
260
+ $log.debug("Formatting with #{mu.inspect}")
261
+
262
+ doc = RDoc::Markup::Document.new
263
+ section = nil
264
+
265
+ items.each_with_index do |i, j|
266
+ $log.debug("Print #{j}: #{i.inspect}")
267
+
268
+ # --li
269
+ break if $opts[:li].positive? && j >= $opts[:li]
270
+
271
+ # Use RDoc::Markup::Verbatim to save the item
272
+ v = RDoc::Markup::Verbatim.new
273
+ v.format = i
274
+ doc << v
275
+
276
+ # Sections
277
+ if $opts[:sep] && section != i[$opts[:sep]]
278
+ $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
279
+
280
+ doc << RDoc::Markup::Raw.new($opts[:sep])
281
+
282
+ doc << RDoc::Markup::BlankLine.new if j.positive?
283
+ doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
284
+ doc << RDoc::Markup::Rule.new(0)
285
+
286
+ section = i[$opts[:sep]]
287
+ end
288
+
289
+ # Item
290
+ props = RDoc::Markup::List.new(:BULLET)
291
+
292
+ # Properties
293
+ $opts[:props].each_with_index do |prop, k|
294
+ value = i[prop]
295
+
296
+ next unless value
297
+ next if Array === value && !value[0]
298
+ next if String === value && value.length == 0
299
+
300
+ $log.debug("#{prop}: #{value}")
301
+
302
+ # Use Raw to save the property
303
+ props << RDoc::Markup::Raw.new(prop)
304
+
305
+ unless k.positive?
306
+ # First property, value only
307
+ props << RDoc::Markup::Heading.new(2, value.to_s)
308
+ else
309
+ props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
310
+ props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless(i['placeholder'])
311
+ end
312
+ end
313
+
314
+ # Print it
315
+ props << RDoc::Markup::BlankLine.new unless props.empty?
316
+
317
+ doc << props
318
+ end
319
+
320
+ 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,7 +8,7 @@ $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
13
  db: "#{ENV['HOME']}/Library/Calendars/Calendar.sqlitedb",
14
14
  debug: Logger::WARN,
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/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.0.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.0.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-09-11 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