icalPal 3.9.2 → 3.9.3
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 +4 -4
- data/README.md +8 -1
- data/bin/icalPal +27 -27
- data/bin/icalpal +27 -27
- data/ext/extconf.rb +5 -5
- data/lib/ToICalPal.rb +1 -1
- data/lib/defaults.rb +4 -3
- data/lib/event.rb +20 -25
- data/lib/icalPal.rb +4 -17
- data/lib/options.rb +36 -24
- data/lib/rdt.rb +13 -10
- data/lib/reminder.rb +10 -12
- data/lib/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4853d1f050b001c63c1890618f5393eaee86c812f86cacf8be8b9da483fc67d1
|
|
4
|
+
data.tar.gz: ae170e19546fc153bdfbcd6cd89e02ac91c694e2c393e6624e31d437a5b03a34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bf5728beb2504a19f2fae851413c26c6ee192fe2f09e066f8f9b1887e3eb16102eb9a89a4eb5e717e727564e8ca4a74bd09dc3d2743974cf2d33a24d7b25b5d
|
|
7
|
+
data.tar.gz: b60b90e37a562ca7df73c53e0e6ade623baa857b3f520a7faded502e7d588c7cbcb3ef2ee02bc9f486bd840f11b4e7638d92f769570e6b216e3bff9b2eaae5af
|
data/README.md
CHANGED
|
@@ -32,12 +32,19 @@ As a system-wide Ruby gem:
|
|
|
32
32
|
gem install icalPal
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
In your home directory:
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
gem install --user-install icalPal
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
As a [Homebrew](https://brew.sh) formula:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
brew tap ajrosen/tap
|
|
45
|
+
brew install icalPal
|
|
46
|
+
```
|
|
47
|
+
|
|
41
48
|
## Features
|
|
42
49
|
|
|
43
50
|
### Compatability with icalBuddy
|
data/bin/icalPal
CHANGED
|
@@ -13,7 +13,7 @@ def r(gem)
|
|
|
13
13
|
$stderr.puts "FATAL: icalPal is missing a dependency: #{gem}"
|
|
14
14
|
$stderr.puts e
|
|
15
15
|
$stderr.puts
|
|
16
|
-
abort "Try installing with 'gem install
|
|
16
|
+
abort "Try installing with 'gem install #{gem}'"
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -63,7 +63,7 @@ $items = [] # Items to be printed
|
|
|
63
63
|
##################################################
|
|
64
64
|
# All kids love log!
|
|
65
65
|
|
|
66
|
-
$log.info("Options
|
|
66
|
+
$log.info("Options:\n#{$opts.to_json}")
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
##################################################
|
|
@@ -90,39 +90,38 @@ $opts[:db].each do |db|
|
|
|
90
90
|
$log.debug("Trying #{db}")
|
|
91
91
|
|
|
92
92
|
if klass == ICalPal::Reminder
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
success = true
|
|
93
|
+
# Load all .sqlite files
|
|
94
|
+
$log.debug("Loading *.sqlite in #{db}")
|
|
95
|
+
Dir.glob("#{db}/*.sqlite").each do |d|
|
|
96
|
+
success = true
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
rows = klass.load_data(d, klass::QUERY)
|
|
99
|
+
$rows += rows
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
sections = klass.load_data(d, klass::SECTIONS_QUERY)
|
|
102
|
+
$sections += sections
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
rescue SQLite3::CantOpenException
|
|
107
|
-
# Non-fatal exception, try the next one
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
rescue Errno::EPERM
|
|
111
|
-
# Probably need Full Disk Access
|
|
104
|
+
$log.info("Loaded #{rows.length} rows and #{sections.length} sections from #{d}")
|
|
112
105
|
end
|
|
113
106
|
else
|
|
114
107
|
# Load database
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
$rows += rows
|
|
108
|
+
rows = ICalPal.load_data(db, klass::QUERY)
|
|
109
|
+
$rows += rows
|
|
118
110
|
|
|
119
|
-
|
|
111
|
+
success = true
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
rescue SQLite3::CantOpenException
|
|
123
|
-
# Non-fatal exception, try the next one
|
|
124
|
-
end
|
|
113
|
+
$log.info("Loaded #{rows.length} rows from #{db}")
|
|
125
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}")
|
|
126
125
|
end
|
|
127
126
|
|
|
128
127
|
# Make sure we opened at least one database
|
|
@@ -153,6 +152,7 @@ unless success
|
|
|
153
152
|
abort
|
|
154
153
|
end
|
|
155
154
|
|
|
155
|
+
$log.debug("Loaded #{$rows.length} #{klass} items")
|
|
156
156
|
$log.info("Window is #{$opts[:from]} to #{$opts[:to]}") if $opts[:from]
|
|
157
157
|
|
|
158
158
|
|
|
@@ -290,7 +290,7 @@ unless mu
|
|
|
290
290
|
when 'remind' then items.map { |i|
|
|
291
291
|
"REM #{i['sdate'].strftime('%F AT %R')} " +
|
|
292
292
|
"DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
|
|
293
|
-
"MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }
|
|
293
|
+
"MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }}"
|
|
294
294
|
}.join("\n")
|
|
295
295
|
else abort "No formatter for #{$opts[:output]}"
|
|
296
296
|
end
|
data/bin/icalpal
CHANGED
|
@@ -13,7 +13,7 @@ def r(gem)
|
|
|
13
13
|
$stderr.puts "FATAL: icalPal is missing a dependency: #{gem}"
|
|
14
14
|
$stderr.puts e
|
|
15
15
|
$stderr.puts
|
|
16
|
-
abort "Try installing with 'gem install
|
|
16
|
+
abort "Try installing with 'gem install #{gem}'"
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -63,7 +63,7 @@ $items = [] # Items to be printed
|
|
|
63
63
|
##################################################
|
|
64
64
|
# All kids love log!
|
|
65
65
|
|
|
66
|
-
$log.info("Options
|
|
66
|
+
$log.info("Options:\n#{$opts.to_json}")
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
##################################################
|
|
@@ -90,39 +90,38 @@ $opts[:db].each do |db|
|
|
|
90
90
|
$log.debug("Trying #{db}")
|
|
91
91
|
|
|
92
92
|
if klass == ICalPal::Reminder
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
success = true
|
|
93
|
+
# Load all .sqlite files
|
|
94
|
+
$log.debug("Loading *.sqlite in #{db}")
|
|
95
|
+
Dir.glob("#{db}/*.sqlite").each do |d|
|
|
96
|
+
success = true
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
rows = klass.load_data(d, klass::QUERY)
|
|
99
|
+
$rows += rows
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
sections = klass.load_data(d, klass::SECTIONS_QUERY)
|
|
102
|
+
$sections += sections
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
rescue SQLite3::CantOpenException
|
|
107
|
-
# Non-fatal exception, try the next one
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
rescue Errno::EPERM
|
|
111
|
-
# Probably need Full Disk Access
|
|
104
|
+
$log.info("Loaded #{rows.length} rows and #{sections.length} sections from #{d}")
|
|
112
105
|
end
|
|
113
106
|
else
|
|
114
107
|
# Load database
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
$rows += rows
|
|
108
|
+
rows = ICalPal.load_data(db, klass::QUERY)
|
|
109
|
+
$rows += rows
|
|
118
110
|
|
|
119
|
-
|
|
111
|
+
success = true
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
rescue SQLite3::CantOpenException
|
|
123
|
-
# Non-fatal exception, try the next one
|
|
124
|
-
end
|
|
113
|
+
$log.info("Loaded #{rows.length} rows from #{db}")
|
|
125
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}")
|
|
126
125
|
end
|
|
127
126
|
|
|
128
127
|
# Make sure we opened at least one database
|
|
@@ -153,6 +152,7 @@ unless success
|
|
|
153
152
|
abort
|
|
154
153
|
end
|
|
155
154
|
|
|
155
|
+
$log.debug("Loaded #{$rows.length} #{klass} items")
|
|
156
156
|
$log.info("Window is #{$opts[:from]} to #{$opts[:to]}") if $opts[:from]
|
|
157
157
|
|
|
158
158
|
|
|
@@ -290,7 +290,7 @@ unless mu
|
|
|
290
290
|
when 'remind' then items.map { |i|
|
|
291
291
|
"REM #{i['sdate'].strftime('%F AT %R')} " +
|
|
292
292
|
"DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
|
|
293
|
-
"MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }
|
|
293
|
+
"MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }}"
|
|
294
294
|
}.join("\n")
|
|
295
295
|
else abort "No formatter for #{$opts[:output]}"
|
|
296
296
|
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
|
|
10
|
+
dependencies = %w[ plist tzinfo ]
|
|
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.
|
|
@@ -33,15 +33,15 @@ begin
|
|
|
33
33
|
# So neither environment can install the other's sqlite3 gem. We
|
|
34
34
|
# must install sqlite3, but iff we are not building with macOS'
|
|
35
35
|
# Ruby installation.
|
|
36
|
-
dependencies.push('sqlite3')
|
|
36
|
+
dependencies.push('sqlite3')
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
di = Gem::DependencyInstaller.new(install_dir: gemdir)
|
|
40
40
|
dependencies.each { |d| di.install(d) }
|
|
41
|
-
rescue Exception
|
|
41
|
+
rescue Exception
|
|
42
42
|
exit(1)
|
|
43
|
-
end
|
|
43
|
+
end
|
|
44
44
|
|
|
45
|
-
File.write(
|
|
45
|
+
File.write('Makefile', "clean:\n\ttrue\ninstall:\n\ttrue")
|
|
46
46
|
|
|
47
47
|
exit(0)
|
data/lib/ToICalPal.rb
CHANGED
|
@@ -76,7 +76,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
|
76
76
|
def accept_list_start(_arg)
|
|
77
77
|
return if @opts[:nb] || @item['placeholder']
|
|
78
78
|
|
|
79
|
-
if @item['due_date'] && (@item['due_date']).between?(0, $
|
|
79
|
+
if @item['due_date'] && (@item['due_date']).between?(0, $nowto_i)
|
|
80
80
|
# Use alert bullet for overdue items
|
|
81
81
|
@res << "#{@opts[:ab]} "
|
|
82
82
|
else
|
data/lib/defaults.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Does anybody really know what time it is?
|
|
2
|
-
now = Time.now
|
|
3
|
-
$
|
|
4
|
-
$
|
|
2
|
+
$now = Time.now
|
|
3
|
+
$nowto_i = $now.to_i
|
|
4
|
+
$nowrdt = ICalPal::RDT.from_time($now)
|
|
5
|
+
$today = $nowrdt.day_start
|
|
5
6
|
|
|
6
7
|
# Defaults
|
|
7
8
|
$defaults = {
|
data/lib/event.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
r 'timezone'
|
|
2
|
-
|
|
3
1
|
module ICalPal
|
|
4
2
|
# Class representing items from the <tt>CalendarItem</tt> table
|
|
5
3
|
class Event
|
|
@@ -13,8 +11,8 @@ module ICalPal
|
|
|
13
11
|
def []=(k, v)
|
|
14
12
|
@self[k] = v
|
|
15
13
|
|
|
16
|
-
@self['sctime'] = Time.at(@self['sdate'].to_i
|
|
17
|
-
@self['ectime'] = Time.at(@self['edate'].to_i
|
|
14
|
+
@self['sctime'] = Time.at(@self['sdate'].to_i) if k == 'sdate'
|
|
15
|
+
@self['ectime'] = Time.at(@self['edate'].to_i) if k == 'edate'
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
# Standard accessor with special handling for +age+,
|
|
@@ -50,17 +48,17 @@ module ICalPal
|
|
|
50
48
|
(@self['notes'])? @self['notes'].strip.gsub("\n", $opts[:nnr]) : nil
|
|
51
49
|
|
|
52
50
|
when 'sday' # pseudo-property
|
|
53
|
-
|
|
51
|
+
@self['sdate'].day_start
|
|
54
52
|
|
|
55
53
|
when 'status' # Integer -> String
|
|
56
54
|
EventKit::EKEventStatus.select { |_k, v| v == @self['status'] }.keys[0]
|
|
57
55
|
|
|
58
|
-
when 'event', 'name', 'title' # title[ (age N)]
|
|
59
|
-
@self['title'] + ((@self['calendar'] == 'Birthdays')? " (age #{self['age']})" : '')
|
|
60
|
-
|
|
61
56
|
when 'uid' # for icalBuddy
|
|
62
57
|
@self['UUID']
|
|
63
58
|
|
|
59
|
+
when 'event', 'name', 'title' # title[ (age N)]
|
|
60
|
+
@self['title'] + ((@self['calendar'] == 'Birthdays')? " (age #{self['age']})" : '')
|
|
61
|
+
|
|
64
62
|
else @self[k]
|
|
65
63
|
end
|
|
66
64
|
end
|
|
@@ -78,7 +76,7 @@ module ICalPal
|
|
|
78
76
|
'sdate' => obj,
|
|
79
77
|
'placeholder' => true,
|
|
80
78
|
'title' => 'Nothing.',
|
|
81
|
-
} if obj.is_a?(DateTime)
|
|
79
|
+
} if obj.is_a?(DateTime) && $opts[:sed]
|
|
82
80
|
|
|
83
81
|
super
|
|
84
82
|
|
|
@@ -92,17 +90,14 @@ module ICalPal
|
|
|
92
90
|
obj.keys.select { |i| i.end_with? '_date' }.each do |k|
|
|
93
91
|
next unless obj[k]
|
|
94
92
|
|
|
95
|
-
begin
|
|
96
|
-
zone = Timezone.fetch(obj['start_tz'])
|
|
97
|
-
rescue Timezone::Error::InvalidZone
|
|
98
|
-
zone = '+00:00'
|
|
99
|
-
end
|
|
100
|
-
|
|
101
93
|
# Save as seconds, Time, RDT
|
|
102
94
|
ctime = obj[k] + ITIME
|
|
95
|
+
ctime -= $now.utc_offset if obj['start_tz'] == '_float'
|
|
96
|
+
ttime = Time.at(ctime)
|
|
97
|
+
|
|
103
98
|
@self["#{k[0]}seconds"] = ctime
|
|
104
|
-
@self["#{k[0]}ctime"] =
|
|
105
|
-
@self["#{k[0]}date"] = RDT.from_time(
|
|
99
|
+
@self["#{k[0]}ctime"] = ttime
|
|
100
|
+
@self["#{k[0]}date"] = RDT.from_time(ttime)
|
|
106
101
|
end
|
|
107
102
|
|
|
108
103
|
@self.delete('unique_identifier')
|
|
@@ -122,14 +117,14 @@ module ICalPal
|
|
|
122
117
|
return events if nDays > 100_000
|
|
123
118
|
|
|
124
119
|
# If multi-day, each (unique) day needs to end at 23:59:59
|
|
125
|
-
self['edate'] = RDT.new(*@self['sdate'].to_a[0..2] + [ 23, 59, 59 ]) if nDays.positive?
|
|
120
|
+
self['edate'] = RDT.new(*@self['sdate'].to_a[0..2] + [ 23, 59, 59 ], @self['sdate'].zone) if nDays.positive?
|
|
126
121
|
|
|
127
122
|
# Repeat for multi-day events
|
|
128
123
|
(nDays + 1).times do |i|
|
|
129
|
-
break
|
|
124
|
+
break unless $opts[:now] || @self['sdate'] <= $opts[:to]
|
|
130
125
|
|
|
131
|
-
if in_window?(self['sdate'], self['edate'])
|
|
132
|
-
self['daynum'] = i + 1 if nDays.positive?
|
|
126
|
+
if in_window?(@self['sdate'], @self['edate'])
|
|
127
|
+
@self['daynum'] = i + 1 if nDays.positive?
|
|
133
128
|
events.push(clone)
|
|
134
129
|
end
|
|
135
130
|
|
|
@@ -156,7 +151,7 @@ module ICalPal
|
|
|
156
151
|
changes = [ { orig_date: -1 } ]
|
|
157
152
|
changes += $rows.select { |r| r['orig_item_id'] == self['ROWID'] }
|
|
158
153
|
|
|
159
|
-
while self['sdate'] <= stop
|
|
154
|
+
while @self['sdate'] <= stop
|
|
160
155
|
# count
|
|
161
156
|
break if self['count'].positive? && count > self['count']
|
|
162
157
|
|
|
@@ -240,8 +235,8 @@ module ICalPal
|
|
|
240
235
|
m = mo.to_i
|
|
241
236
|
|
|
242
237
|
# Set dates to the first of <m>
|
|
243
|
-
nsdate = RDT.new(self['sdate'].year, m, 1, self['sdate'].hour, self['sdate'].
|
|
244
|
-
nedate = RDT.new(self['edate'].year, m, 1, self['edate'].hour, self['edate'].
|
|
238
|
+
nsdate = RDT.new(@self['sdate'].year, m, 1, @self['sdate'].hour, @self['sdate'].min, @self['sdate'].sec, @self['sdate'].zone)
|
|
239
|
+
nedate = RDT.new(@self['edate'].year, m, 1, @self['edate'].hour, @self['edate'].min, @self['edate'].sec, @self['edate'].zone)
|
|
245
240
|
|
|
246
241
|
# ...but not in the past
|
|
247
242
|
nsdate >>= 12 if nsdate.month < m
|
|
@@ -305,7 +300,7 @@ module ICalPal
|
|
|
305
300
|
# @return [Boolean]
|
|
306
301
|
def in_window?(s, e)
|
|
307
302
|
if $opts[:now]
|
|
308
|
-
if (
|
|
303
|
+
if $nowto_i.between?(s.to_i, e.to_i)
|
|
309
304
|
$log.debug("now: #{s} to #{e} vs. #{$now}")
|
|
310
305
|
true
|
|
311
306
|
else
|
data/lib/icalPal.rb
CHANGED
|
@@ -40,6 +40,8 @@ module ICalPal
|
|
|
40
40
|
|
|
41
41
|
# Prepare the query
|
|
42
42
|
stmt = db.prepare(q)
|
|
43
|
+
|
|
44
|
+
# Check for "list" and "all" pseudo-properties
|
|
43
45
|
abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
|
|
44
46
|
$opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
|
|
45
47
|
|
|
@@ -50,21 +52,6 @@ module ICalPal
|
|
|
50
52
|
# Close the database
|
|
51
53
|
db.close
|
|
52
54
|
$log.debug("Closed #{db_file}")
|
|
53
|
-
|
|
54
|
-
rescue SQLite3::BusyException => e
|
|
55
|
-
$log.error("Non-fatal error closing database #{db.filename}")
|
|
56
|
-
raise e
|
|
57
|
-
|
|
58
|
-
rescue SQLite3::CantOpenException => e
|
|
59
|
-
$log.debug("Can't open #{db_file}")
|
|
60
|
-
raise e
|
|
61
|
-
|
|
62
|
-
rescue SQLite3::SQLException => e
|
|
63
|
-
$log.info("#{db_file}: #{e}")
|
|
64
|
-
raise e
|
|
65
|
-
|
|
66
|
-
rescue SQLite3::Exception => e
|
|
67
|
-
abort("#{db_file}: #{e}")
|
|
68
55
|
end
|
|
69
56
|
|
|
70
57
|
rows
|
|
@@ -123,7 +110,7 @@ module ICalPal
|
|
|
123
110
|
def self.nth(n, dow, m)
|
|
124
111
|
# Get the number of days in the month by advancing to the first of
|
|
125
112
|
# the next month, then going back one day
|
|
126
|
-
a = [ RDT.new(m.year, m.month, 1, m.hour, m.
|
|
113
|
+
a = [ RDT.new(m.year, m.month, 1, m.hour, m.min, m.sec, m.zone) ]
|
|
127
114
|
a[1] = (a[0] >> 1) - 1
|
|
128
115
|
|
|
129
116
|
# Reverse it if going backwards
|
|
@@ -137,7 +124,7 @@ module ICalPal
|
|
|
137
124
|
end
|
|
138
125
|
end
|
|
139
126
|
|
|
140
|
-
# Epoch + 31 years
|
|
127
|
+
# Epoch + 31 years (Mon Jan 1 00:00:00 UTC 2001)
|
|
141
128
|
ITIME = 978_307_200
|
|
142
129
|
|
|
143
130
|
# Days of the week abbreviations used in recurrence rules
|
data/lib/options.rb
CHANGED
|
@@ -239,7 +239,7 @@ module ICalPal
|
|
|
239
239
|
|
|
240
240
|
@op.parse!(o, into: cf)
|
|
241
241
|
rescue StandardError
|
|
242
|
-
end unless cli[:norc]
|
|
242
|
+
end unless cli[:norc] && !cli[:cf]
|
|
243
243
|
|
|
244
244
|
# Find command
|
|
245
245
|
cli[:cmd] ||= @op.default_argv[0]
|
|
@@ -254,22 +254,31 @@ module ICalPal
|
|
|
254
254
|
cli[:cmd] = cli[:cmd].sub('reminders', 'tasks')
|
|
255
255
|
cli[:cmd] = cli[:cmd].sub('datedReminders', 'datedTasks')
|
|
256
256
|
|
|
257
|
-
#
|
|
258
|
-
cli[:cmd].match('events(Now|Today|Remaining)(
|
|
259
|
-
cli[:
|
|
260
|
-
cli[:days] = (m[1] == 'Today')? m[2].to_i : 1
|
|
257
|
+
# Handle events command variants
|
|
258
|
+
cli[:cmd].match('events(?<v>Now|Today|Remaining)(?<n>\+[0-9]+)?') do |m|
|
|
259
|
+
cli[:cmd] = 'events'
|
|
261
260
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
cli[:
|
|
265
|
-
end
|
|
261
|
+
case m.named_captures['v']
|
|
262
|
+
when 'Now'
|
|
263
|
+
cli[:now] = true
|
|
266
264
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
when 'Today'
|
|
266
|
+
cli[:from] = $today
|
|
267
|
+
cli[:days] = (m.named_captures['n'])? m.named_captures['n'].to_i : 1
|
|
270
268
|
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
when 'Remaining'
|
|
270
|
+
cli[:from] = RDT.from_time($now)
|
|
271
|
+
cli[:to] = $today.day_end
|
|
272
|
+
cli[:days] = 1
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Handle tasks command variants
|
|
277
|
+
if cli[:cmd] == 'tasksDueBefore'
|
|
278
|
+
cli.delete(:days) unless cli[:days]
|
|
279
|
+
cli[:from] = RDT.from_epoch(0) unless cli[:from]
|
|
280
|
+
end
|
|
281
|
+
cli[:cmd] = 'tasks' if %w[ datedTasks undatedTasks tasksDueBefore ].include? cli[:cmd]
|
|
273
282
|
|
|
274
283
|
# Must have a valid command
|
|
275
284
|
raise(OptionParser::InvalidArgument, "Unknown COMMAND #{cli[:cmd]}") unless (COMMANDS.any? cli[:cmd])
|
|
@@ -281,13 +290,6 @@ module ICalPal
|
|
|
281
290
|
.merge(env)
|
|
282
291
|
.merge(cli)
|
|
283
292
|
|
|
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]
|
|
290
|
-
|
|
291
293
|
# Make sure opts[:db] and opts[:tasks] are Arrays
|
|
292
294
|
opts[:db] = [ opts[:db] ] unless opts[:db].is_a?(Array)
|
|
293
295
|
opts[:tasks] = [ opts[:tasks] ] unless opts[:db].is_a?(Array)
|
|
@@ -310,11 +312,19 @@ module ICalPal
|
|
|
310
312
|
opts[:days] -= 1 if opts[:days]
|
|
311
313
|
|
|
312
314
|
if opts[:from]
|
|
315
|
+
# -n
|
|
316
|
+
opts[:from] = RDT.from_time($now) if opts[:n]
|
|
317
|
+
|
|
318
|
+
# Default :to is :from + 1 day
|
|
319
|
+
# --days overrides
|
|
313
320
|
opts[:to] ||= opts[:from] + 1 if opts[:from]
|
|
314
321
|
opts[:to] = opts[:from] + opts[:days] if opts[:days]
|
|
315
|
-
|
|
322
|
+
|
|
323
|
+
# Make :to be end of day
|
|
324
|
+
opts[:to] = opts[:to].day_end
|
|
325
|
+
|
|
326
|
+
# Calculate days unless specified
|
|
316
327
|
opts[:days] ||= Integer(opts[:to] - opts[:from])
|
|
317
|
-
opts[:from] = $now if opts[:n]
|
|
318
328
|
end
|
|
319
329
|
|
|
320
330
|
# Sorting
|
|
@@ -348,7 +358,9 @@ module ICalPal
|
|
|
348
358
|
end
|
|
349
359
|
|
|
350
360
|
# Commands that can be run
|
|
351
|
-
COMMANDS = %w[events eventsToday eventsNow eventsRemaining
|
|
361
|
+
COMMANDS = %w[events eventsToday eventsNow eventsRemaining
|
|
362
|
+
tasks datedTasks undatedTasks tasksDueBefore
|
|
363
|
+
calendars accounts].freeze
|
|
352
364
|
|
|
353
365
|
# Supported output formats
|
|
354
366
|
OUTFORMATS = %w[ansi csv default hash html json md rdoc remind toc xml yaml].freeze
|
data/lib/rdt.rb
CHANGED
|
@@ -4,7 +4,7 @@ module ICalPal
|
|
|
4
4
|
|
|
5
5
|
# Create a new RDT from a Time object
|
|
6
6
|
def self.from_time(t)
|
|
7
|
-
new(*t.to_a[0..5].reverse)
|
|
7
|
+
new(*t.to_a[0..5].reverse, (t.gmt_offset / 3600).to_s)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
# Create a new RDT from seconds since epoch
|
|
@@ -42,8 +42,9 @@ module ICalPal
|
|
|
42
42
|
# today.
|
|
43
43
|
def to_s
|
|
44
44
|
return strftime($opts[:df]) if $opts && $opts[:nrd] && $opts[:df]
|
|
45
|
+
return super unless $today && $opts
|
|
45
46
|
|
|
46
|
-
case Integer(RDT.new(
|
|
47
|
+
case Integer(RDT.new(*ymd, month, day) - $today)
|
|
47
48
|
when -2 then 'day before yesterday'
|
|
48
49
|
when -1 then 'yesterday'
|
|
49
50
|
when 0 then 'today'
|
|
@@ -67,17 +68,19 @@ module ICalPal
|
|
|
67
68
|
to_time.to_i
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
# @return [
|
|
71
|
-
def
|
|
72
|
-
|
|
71
|
+
# @return [RDT] Self at 00:00:00
|
|
72
|
+
def day_start
|
|
73
|
+
RDT.new(year, month, day, 0, 0, 0, zone)
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
# @
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def ==(other)
|
|
79
|
-
self.to_s == other.to_s
|
|
76
|
+
# @return [RDT] Self at 23:59:59
|
|
77
|
+
def day_end
|
|
78
|
+
RDT.new(year, month, day, 23, 59, 59, zone)
|
|
80
79
|
end
|
|
81
80
|
|
|
81
|
+
# @return [Array] Only the year, month and day of self
|
|
82
|
+
def ymd
|
|
83
|
+
[ year, month, day ]
|
|
84
|
+
end
|
|
82
85
|
end
|
|
83
86
|
end
|
data/lib/reminder.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
r '
|
|
1
|
+
r 'tzinfo'
|
|
2
2
|
|
|
3
3
|
module ICalPal
|
|
4
4
|
# Class representing items from the <tt>Reminders</tt> database
|
|
@@ -8,6 +8,12 @@ module ICalPal
|
|
|
8
8
|
def self.load_data(db_file, q)
|
|
9
9
|
# Load items
|
|
10
10
|
ICalPal.load_data(db_file, q)
|
|
11
|
+
|
|
12
|
+
rescue SQLite3::SQLException => e
|
|
13
|
+
# Data-local.sqlite does not have zremcdBaseList
|
|
14
|
+
raise e unless e.message =~ /no such table/
|
|
15
|
+
|
|
16
|
+
[]
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
def [](k)
|
|
@@ -91,8 +97,8 @@ module ICalPal
|
|
|
91
97
|
if @self['due_date']
|
|
92
98
|
begin
|
|
93
99
|
@self['due_date'] += ITIME
|
|
94
|
-
zone = Timezone.
|
|
95
|
-
rescue
|
|
100
|
+
zone = TZInfo::Timezone.get(@self['timezone'])
|
|
101
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
|
96
102
|
zone = '+00:00'
|
|
97
103
|
end
|
|
98
104
|
|
|
@@ -169,15 +175,12 @@ bl1.zParentList as parent,
|
|
|
169
175
|
bl1.zSharingStatus as shared,
|
|
170
176
|
bl1.zShouldCategorizeGroceryItems as grocery,
|
|
171
177
|
|
|
172
|
-
-- section members
|
|
173
178
|
json(bl1.ZMembershipsOfRemindersInSectionsAsData) -> '$.memberships' AS members,
|
|
174
179
|
|
|
175
|
-
-- group
|
|
176
180
|
(SELECT zName
|
|
177
181
|
FROM zremcdBaseList bl2
|
|
178
182
|
WHERE bl2.z_pk = bl1.zParentList) AS 'group',
|
|
179
183
|
|
|
180
|
-
-- location
|
|
181
184
|
(SELECT json_group_array(zremcdObject.zTitle)
|
|
182
185
|
FROM zremcdObject
|
|
183
186
|
WHERE zremcdObject.z_pk IN (
|
|
@@ -186,7 +189,6 @@ WHERE zremcdObject.z_pk IN (
|
|
|
186
189
|
WHERE zremcdObject.zReminder = r1.z_pk
|
|
187
190
|
)) AS location,
|
|
188
191
|
|
|
189
|
-
-- proximity
|
|
190
192
|
(SELECT json_group_array(zremcdObject.zProximity)
|
|
191
193
|
FROM zremcdObject
|
|
192
194
|
WHERE zremcdObject.z_pk IN (
|
|
@@ -195,7 +197,6 @@ WHERE zremcdObject.z_pk IN (
|
|
|
195
197
|
WHERE zremcdObject.zReminder = r1.z_pk
|
|
196
198
|
)) AS proximity,
|
|
197
199
|
|
|
198
|
-
-- radius
|
|
199
200
|
(SELECT json_group_array(zremcdObject.zRadius)
|
|
200
201
|
FROM zremcdObject
|
|
201
202
|
WHERE zremcdObject.z_pk IN (
|
|
@@ -204,17 +205,15 @@ WHERE zremcdObject.z_pk IN (
|
|
|
204
205
|
WHERE zremcdObject.zReminder = r1.z_pk
|
|
205
206
|
)) AS radius,
|
|
206
207
|
|
|
207
|
-
-- tags
|
|
208
208
|
(SELECT json_group_array(zName)
|
|
209
209
|
FROM zremcdHashtagLabel
|
|
210
210
|
WHERE zremcdHashtagLabel.z_pk IN (
|
|
211
211
|
SELECT zremcdObject.zHashtagLabel
|
|
212
212
|
FROM zremcdObject
|
|
213
|
-
JOIN
|
|
213
|
+
JOIN zremcdReminder ON zremcdObject.zReminder3 = r1.z_pk
|
|
214
214
|
WHERE zremcdObject.zReminder3 = r1.z_pk
|
|
215
215
|
)) AS tags,
|
|
216
216
|
|
|
217
|
-
-- assignee
|
|
218
217
|
(SELECT
|
|
219
218
|
json_array(zNickname, zFirstName, zLastName, zAddress1)
|
|
220
219
|
FROM zremcdObject
|
|
@@ -224,7 +223,6 @@ WHERE zremcdObject.z_pk IN (
|
|
|
224
223
|
WHERE zReminder1 = r1.z_pk
|
|
225
224
|
)) AS assignee,
|
|
226
225
|
|
|
227
|
-
-- url
|
|
228
226
|
(SELECT zURL
|
|
229
227
|
FROM zremcdObject
|
|
230
228
|
WHERE zReminder2 = r1.z_pk) AS url
|
data/lib/version.rb
CHANGED
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.9.
|
|
4
|
+
version: 3.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Rosen
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: |
|
|
13
13
|
Inspired by icalBuddy and maintains close compatability. Includes
|
|
@@ -62,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
63
|
version: '0'
|
|
64
64
|
requirements: []
|
|
65
|
-
rubygems_version: 3.
|
|
65
|
+
rubygems_version: 3.7.2
|
|
66
66
|
specification_version: 4
|
|
67
67
|
summary: Command-line tool to query the macOS Calendar and Reminders
|
|
68
68
|
test_files: []
|