icalPal 1.2.0 → 1.2.1

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: a6fac725e88ea9664672d6a52877cb99c2278fe851516316cf51e9e264bb4cd1
4
- data.tar.gz: 7332400d9ee46f3aeebad92c2c2098e90e64b721d1cf455ab74a1e231365686c
3
+ metadata.gz: c08ab4aaf0558f9648fbefbb9eaea1aac9c50de2770e1897699b619ba92ef6b6
4
+ data.tar.gz: '087d14b6e855bdbf2681e526941baec600fed607a6e6760b09f4361bb33735d8'
5
5
  SHA512:
6
- metadata.gz: db8c28fdeeba37d4a7b3a10dffc1651fd2c8f1b1ede100eceb00e3ee99e853aa2b797e8421fa9bfaab0579775e0ded38ed7e18909f342a682a24540dc0f589a1
7
- data.tar.gz: dc7aa7cc84b653929904a2eeb33407a576bff18a78fb5b58f38b7ffda9d6cc6b4afd3e267977a32f66f13baf2788b1bff4ca9f43ed037aff9e71179cbf01143d
6
+ metadata.gz: 70934664b432d1b99e0f200bd1e78a2b142f9b1b9b405c8763913aa9d414a83171f4876e0145ff741854abd1b643fdb4a72031b564af14a2a4f1764cc494a13f
7
+ data.tar.gz: '0580e0d9556919516b1c2d4a4ea11306a2a8786ea80ee5ccc5821c97f899d7bc95c3499c7f5d9dc05ac091dba53867b01a7b676e0bd20881563d7529e16a1a31'
data/bin/icalPal CHANGED
@@ -59,45 +59,91 @@ q = klass::QUERY
59
59
  $log.debug(q.gsub(/\n/, ' '))
60
60
 
61
61
  # Get it
62
- stmt = $db.prepare(q)
63
- abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
64
- $opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
62
+ begin
63
+ stmt = $db.prepare(q)
64
+ abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
65
+ $opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
66
+
67
+ # Iterate the SQLite3::ResultSet once
68
+ stmt.execute.each_with_index { |i, j| $rows[j] = i }
69
+ stmt.close
65
70
 
66
- # Iterate the SQLite3::ResultSet once
67
- stmt.execute.each_with_index { |i, j| $rows[j] = i }
68
- stmt.close
71
+ # Close the database
72
+ $db.close
73
+ $log.debug("Closed #{$opts[:db]}")
69
74
 
70
- $log.info("Loaded #{$rows.count} rows from #{$db.filename}")
71
- $db.close
75
+ rescue SQLite3::BusyException => e
76
+ $log.error("Non-fatal error closing database #{$db.filename}")
77
+
78
+ rescue SQLite3::SQLException => e
79
+ abort(e.message)
80
+ end
81
+
82
+ $log.info("Loaded #{$rows.count} rows from #{$opts[:db]}")
72
83
 
73
84
 
74
85
  ##################################################
75
86
  # Process the data
76
87
 
77
88
  # Add rows
78
- $rows.each do |row|
89
+ $rows.each_with_index do |row, i|
90
+ $log.debug("Row #{i}: #{row['ROWID']}:#{row['UUID']} - #{row['account']}/#{row['calendar']}/#{row['title']}")
91
+
79
92
  # --es/--is
80
- next if $opts[:es].any? row['account']
81
- next unless $opts[:is].empty? or $opts[:is].any? row['account']
93
+ if $opts[:es].any? row['account'] then
94
+ $log.debug(":es")
95
+ next
96
+ end
97
+
98
+ unless $opts[:is].empty? or $opts[:is].any? row['account']
99
+ $log.debug(":is");
100
+ next
101
+ end
82
102
 
83
103
  # --ec/--ic
84
- next if $opts[:ec].any? row['calendar']
85
- next unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
104
+ if $opts[:ec].any? row['calendar'] then
105
+ $log.debug(":ec")
106
+ next
107
+ end
108
+
109
+ unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
110
+ $log.debug(":ic")
111
+ next
112
+ end
86
113
 
87
114
  item = klass.new(row)
88
115
 
89
116
  # --et/--it
90
- next if $opts[:et].any? item['type']
91
- next unless $opts[:it].empty? or $opts[:it].any? item['type']
117
+ if $opts[:et].any? item['type'] then
118
+ $log.debug(":et")
119
+ next
120
+ end
121
+
122
+ unless $opts[:it].empty? or $opts[:it].any? item['type']
123
+ $log.debug(":it")
124
+ next
125
+ end
92
126
 
93
127
  unless ICalPal::Event === item
94
128
  # Always add non-event items
129
+ $log.debug("Adding non-event #{item}")
95
130
  add(item)
96
131
  else
97
132
  # Check for all-day and cancelled events
98
- next if $opts[:ea] && item['all_day'].positive?
99
- next if $opts[:ia] && !item['all_day'].positive?
100
- next if item['status'] == :canceled
133
+ if $opts[:ea] && item['all_day'].positive? then
134
+ $log.debug(":ea")
135
+ next
136
+ end
137
+
138
+ if $opts[:ia] && !item['all_day'].positive? then
139
+ $log.debug(":ia")
140
+ next
141
+ end
142
+
143
+ if item['status'] == :canceled then
144
+ $log.debug(":canceled")
145
+ next
146
+ end
101
147
 
102
148
  (item['has_recurrences'].positive?)?
103
149
  item.recurring.each { |i| add(i) } :
@@ -111,12 +157,14 @@ if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
111
157
 
112
158
  $opts[:days].times do |n|
113
159
  day = $opts[:from] + n
114
- $items.push(klass.new(day)) unless days.any? { |i| i == day }
160
+ $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
115
161
  end
116
162
  end
117
163
 
118
164
  # Sort the rows
119
165
  begin
166
+ $log.debug("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
167
+
120
168
  $items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
121
169
  $items.reverse! if $opts[:reverse]
122
170
  $items.uniq!
@@ -124,6 +172,8 @@ rescue ArgumentError => e
124
172
  $log.warn("Sorting failed, results may be unexpected\n")
125
173
  end
126
174
 
175
+ $log.debug("#{$items.count} items remain")
176
+
127
177
  # Configure formatting
128
178
  mu = case $opts[:output]
129
179
  when 'ansi' then RDoc::Markup::ToAnsi.new
@@ -145,15 +195,20 @@ mu = case $opts[:output]
145
195
  items = $items[0..$opts[:li] - 1]
146
196
 
147
197
  unless mu
198
+ $log.debug("Output in #{$opts[:output]} format")
199
+
148
200
  puts case $opts[:output]
149
201
  when 'csv' then
150
- o = {
151
- headers: items[0].keys,
152
- write_converters: proc { |f| f.respond_to?(:gsub)? f.gsub(/\n/, '\n') : f },
153
- write_headers: true,
154
- }
202
+ # Get all headers
203
+ headers = []
204
+ items.each { |i| headers += i.keys }
205
+ headers.uniq!
155
206
 
156
- CSV.generate(**o) { |k| items.each { |i| k << i.values.map { |v| v.to_s } } }
207
+ # Populate a CSV::Table
208
+ table = CSV::Table.new([], headers: headers)
209
+ items.each { |i| table << i.to_csv(headers) }
210
+
211
+ table
157
212
  when 'hash' then items.map { |i| i.self }
158
213
  when 'json' then items.map { |i| i.self }.to_json
159
214
  when 'yaml' then items.map { |i| i.self }.to_yaml
@@ -168,9 +223,13 @@ unless mu
168
223
  exit
169
224
  end
170
225
 
226
+ $log.debug("Formatting with #{mu.inspect}")
227
+
171
228
  section = nil if $opts[:sep]
172
229
 
173
230
  items.each_with_index do |i, j|
231
+ $log.debug("Print #{j}: #{i.inspect}")
232
+
174
233
  # --li
175
234
  break if $opts[:li].positive? && j >= $opts[:li]
176
235
 
@@ -178,7 +237,7 @@ items.each_with_index do |i, j|
178
237
 
179
238
  # Sections
180
239
  if $opts[:sep] && section != i[$opts[:sep]]
181
- $log.debug("Section: #{i[$opts[:sep]]}")
240
+ $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
182
241
 
183
242
  v = RDoc::Markup::Verbatim.new
184
243
  v.format = { item: i, prop: $opts[:sep] }
@@ -210,7 +269,7 @@ items.each_with_index do |i, j|
210
269
  props << RDoc::Markup::Heading.new(2, i[prop].to_s)
211
270
  else
212
271
  props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
213
- props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(i[prop]))
272
+ props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(i[prop])) unless(i['placeholder'])
214
273
  end
215
274
  end
216
275
 
data/icalPal.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "icalPal"
3
- s.version = "1.2.0"
3
+ s.version = "1.2.1"
4
4
  s.summary = "Command-line tool to query the macOS Calendar"
5
5
  s.description = <<-EOF
6
6
  Inspired by icalBuddy and maintains close compatability. Includes
data/lib/event.rb CHANGED
@@ -3,6 +3,11 @@ module ICalPal
3
3
  class Event
4
4
  include ICalPal
5
5
 
6
+ # Standard accessor with special handling for +sdate+. Setting
7
+ # +sdate+ will also set +sday+.
8
+ #
9
+ # @param k [String] Key/property name
10
+ # @param v [Object] Key/property value
6
11
  def []=(k, v)
7
12
  @self[k] = v
8
13
  @self['sday'] = ICalPal::RDT.new(*self['sdate'].to_a[0..2]) if k == 'sdate'
@@ -27,7 +32,7 @@ module ICalPal
27
32
  t += ' at ' unless @self['all_day'].positive?
28
33
  end
29
34
 
30
- unless @self['all_day'] && @self['all_day'].positive?
35
+ unless @self['all_day'] && @self['all_day'].positive? || @self['placeholder']
31
36
  t ||= ''
32
37
  t += "#{@self['sdate'].strftime($opts[:tf])}" if @self['sdate']
33
38
  t += " - #{@self['edate'].strftime($opts[:tf])}" unless $opts[:eed] || !@self['edate']
@@ -65,10 +70,11 @@ module ICalPal
65
70
  def initialize(obj)
66
71
  # Placeholder for days with no events
67
72
  return @self = {
68
- $opts[:sep] => obj,
69
- 'placeholder' => true,
70
- 'title' => 'Nothing.',
71
- } if DateTime === obj
73
+ $opts[:sep] => obj,
74
+ 'sdate' => obj,
75
+ 'placeholder' => true,
76
+ 'title' => 'Nothing.',
77
+ } if DateTime === obj
72
78
 
73
79
  @self = {}
74
80
  obj.keys.each { |k| @self[k] = obj[k] }
@@ -108,6 +114,7 @@ module ICalPal
108
114
 
109
115
  # Repeat for multi-day events
110
116
  ((self['duration'] / 86400).to_i + 1).times do |i|
117
+ $log.debug("multi-day event #{i + 1}") if (i > 0)
111
118
  self['daynum'] = i + 1
112
119
  retval.push(clone) if in_window?(self['sdate'])
113
120
  self['sdate'] += 1
@@ -126,14 +133,20 @@ module ICalPal
126
133
 
127
134
  # See if event ends before we start
128
135
  stop = [ $opts[:to], (self['rdate'] || $opts[:to]) ].min
129
- return(retval) if stop < $opts[:from]
136
+ if stop < $opts[:from] then
137
+ $log.debug("#{stop} < #{$opts[:from]}")
138
+ return(retval)
139
+ end
130
140
 
131
141
  # Get changes to series
132
142
  changes = $rows.select { |r| r['orig_item_id'] == self['ROWID'] }
133
143
 
134
144
  i = 1
135
145
  while self['sdate'] <= stop
136
- return(retval) if self['count'].positive? && i > self['count']
146
+ if self['count'].positive? && i > self['count'] then
147
+ $log.debug("count exceeded: #{i} > #{self['count']}")
148
+ return(retval)
149
+ end
137
150
  i += 1
138
151
 
139
152
  unless @self['xdate'].any?(@self['sdate']) # Exceptions?
@@ -179,6 +192,9 @@ module ICalPal
179
192
  when 'O' then ndate = RDT.new(ndate.year, j[1].to_i, ndate.day)
180
193
  when 'S' then @self['specifier'].sub!(/D=0/, "D=+#{j[1].to_i}")
181
194
  end
195
+
196
+ # No time travel!
197
+ ndate = self['sdate'] if ndate <= self['sdate']
182
198
  end
183
199
 
184
200
  # D=Day of the week
@@ -236,9 +252,23 @@ module ICalPal
236
252
  # @param e [RDT] Event end
237
253
  # @return [Boolean]
238
254
  def in_window?(s, e = s)
239
- $opts[:n]?
240
- ($now >= s && $now < e) :
241
- ([ s, e ].max >= $opts[:from] && s < $opts[:to])
255
+ if $opts[:n] then
256
+ if ($now >= s && $now < e) then
257
+ $log.debug("now: #{s} to #{e} vs. #{$now}")
258
+ return(true)
259
+ else
260
+ $log.debug("not now: #{s} to #{e} vs. #{$now}")
261
+ return(false)
262
+ end
263
+ else
264
+ if ([ s, e ].max >= $opts[:from] && s < $opts[:to]) then
265
+ $log.debug("in window: #{s} to #{e} vs. #{$opts[:from]} to #{$opts[:to]}")
266
+ return(true)
267
+ else
268
+ $log.debug("not in window: #{s} to #{e} vs. #{$opts[:from]} to #{$opts[:to]}")
269
+ return(false)
270
+ end
271
+ end
242
272
  end
243
273
 
244
274
  QUERY = <<~SQL
data/lib/icalPal.rb CHANGED
@@ -43,6 +43,18 @@ module ICalPal
43
43
  @self = obj
44
44
  end
45
45
 
46
+ # Create a new CSV::Row with values from +self+. Newlines are
47
+ # replaced with '\n' to ensure each Row is a single line of text.
48
+ #
49
+ # @param headers [Array] Key names used as the header row in a CSV::Table
50
+ # @return [CSV::Row] The +Store+, +Calendar+, or +CalendarItem+ as a CSV::Row
51
+ def to_csv(headers)
52
+ values = []
53
+ headers.each { |h| values.push(@self[h].respond_to?(:gsub)? @self[h].gsub(/\n/, '\n') : @self[h]) }
54
+
55
+ CSV::Row::new(headers, values)
56
+ end
57
+
46
58
  # Get the +n+'th +dow+ in month +m+
47
59
  #
48
60
  # @param n [Integer] Integer between -4 and +4
data/lib/options.rb CHANGED
@@ -23,7 +23,7 @@ module ICalPal
23
23
  @op = OptionParser.new
24
24
  @op.summary_width = 23
25
25
  @op.banner += " [-c] COMMAND"
26
- @op.version = '1.2.0'
26
+ @op.version = '1.2.1'
27
27
 
28
28
  @op.accept(ICalPal::RDT) { |s| ICalPal::RDT.conv(s) }
29
29
 
data/lib/rdt.rb CHANGED
@@ -52,5 +52,12 @@ module ICalPal
52
52
  def to_i
53
53
  to_time.to_i
54
54
  end
55
+
56
+ # @see ICalPal::RDT.to_s
57
+ #
58
+ # @return [Boolean]
59
+ def ==(obj)
60
+ self.to_s == obj.to_s
61
+ end
55
62
  end
56
63
  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: 1.2.0
4
+ version: 1.2.1
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-04-05 00:00:00.000000000 Z
11
+ date: 2024-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3