icalPal 1.2.0 → 1.2.1

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: 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